-
Notifications
You must be signed in to change notification settings - Fork 49
/
indexing.py
800 lines (647 loc) · 33.7 KB
/
indexing.py
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
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
# -*- coding: utf-8 -*-
"""
keri.core.indexing module
Provides versioning support for Indexer classes and codes
"""
from collections import namedtuple, deque
from dataclasses import dataclass, astuple, asdict
from base64 import urlsafe_b64encode as encodeB64
from base64 import urlsafe_b64decode as decodeB64
import pysodium
from ..kering import (EmptyMaterialError, RawMaterialError, SoftMaterialError,
InvalidCodeError, InvalidSoftError,
InvalidSizeError,
InvalidCodeSizeError, InvalidVarIndexError,
InvalidVarSizeError, InvalidVarRawSizeError,
ConversionError, InvalidValueError, InvalidTypeError,
ValidationError, VersionError, DerivationError,
EmptyListError,
ShortageError, UnexpectedCodeError, DeserializeError,
UnexpectedCountCodeError, UnexpectedOpCodeError)
from ..help import helping
from ..help.helping import (sceil, intToB64, b64ToInt,
codeB64ToB2, codeB2ToB64, nabSextets)
@dataclass(frozen=True)
class IndexerCodex:
""" IndexerCodex is codex hard (stable) part of all indexer derivation codes.
Codes indicate which list of keys, current and/or prior next, index is for:
_Sig: Indices in code may appear in both current signing and
prior next key lists when event has both current and prior
next key lists. Two character code table has only one index
so must be the same for both lists. Other index if for
prior next.
The indices may be different in those code tables which
have two sets of indices.
_Crt_Sig: Index in code for current signing key list only.
_Big_: Big index values
Only provide defined codes.
Undefined are left out so that inclusion(exclusion) via 'in' operator works.
"""
Ed25519_Sig: str = 'A' # Ed25519 sig appears same in both lists if any.
Ed25519_Crt_Sig: str = 'B' # Ed25519 sig appears in current list only.
ECDSA_256k1_Sig: str = 'C' # ECDSA secp256k1 sig appears same in both lists if any.
ECDSA_256k1_Crt_Sig: str = 'D' # ECDSA secp256k1 sig appears in current list.
ECDSA_256r1_Sig: str = "E" # ECDSA secp256r1 sig appears same in both lists if any.
ECDSA_256r1_Crt_Sig: str = "F" # ECDSA secp256r1 sig appears in current list.
Ed448_Sig: str = '0A' # Ed448 signature appears in both lists.
Ed448_Crt_Sig: str = '0B' # Ed448 signature appears in current list only.
Ed25519_Big_Sig: str = '2A' # Ed25519 sig appears in both lists.
Ed25519_Big_Crt_Sig: str = '2B' # Ed25519 sig appears in current list only.
ECDSA_256k1_Big_Sig: str = '2C' # ECDSA secp256k1 sig appears in both lists.
ECDSA_256k1_Big_Crt_Sig: str = '2D' # ECDSA secp256k1 sig appears in current list only.
ECDSA_256r1_Big_Sig: str = "2E" # ECDSA secp256r1 sig appears in both lists.
ECDSA_256r1_Big_Crt_Sig: str = "2F" # ECDSA secp256r1 sig appears in current list only.
Ed448_Big_Sig: str = '3A' # Ed448 signature appears in both lists.
Ed448_Big_Crt_Sig: str = '3B' # Ed448 signature appears in current list only.
TBD0: str = '0z' # Test of Var len label L=N*4 <= 4095 char quadlets includes code
TBD1: str = '1z' # Test of index sig lead 1
TBD4: str = '4z' # Test of index sig lead 1 big
def __iter__(self):
return iter(astuple(self)) # enables inclusion test with "in"
IdrDex = IndexerCodex()
@dataclass(frozen=True)
class IndexedSigCodex:
"""IndexedSigCodex is codex all indexed signature derivation codes.
Only provide defined codes.
Undefined are left out so that inclusion(exclusion) via 'in' operator works.
"""
Ed25519_Sig: str = 'A' # Ed25519 sig appears same in both lists if any.
Ed25519_Crt_Sig: str = 'B' # Ed25519 sig appears in current list only.
ECDSA_256k1_Sig: str = 'C' # ECDSA secp256k1 sig appears same in both lists if any.
ECDSA_256k1_Crt_Sig: str = 'D' # ECDSA secp256k1 sig appears in current list.
ECDSA_256r1_Sig: str = "E" # ECDSA secp256r1 sig appears same in both lists if any.
ECDSA_256r1_Crt_Sig: str = "F" # ECDSA secp256r1 sig appears in current list.
Ed448_Sig: str = '0A' # Ed448 signature appears in both lists.
Ed448_Crt_Sig: str = '0B' # Ed448 signature appears in current list only.
Ed25519_Big_Sig: str = '2A' # Ed25519 sig appears in both lists.
Ed25519_Big_Crt_Sig: str = '2B' # Ed25519 sig appears in current list only.
ECDSA_256k1_Big_Sig: str = '2C' # ECDSA secp256k1 sig appears in both lists.
ECDSA_256k1_Big_Crt_Sig: str = '2D' # ECDSA secp256k1 sig appears in current list only.
ECDSA_256r1_Big_Sig: str = "2E" # ECDSA secp256r1 sig appears in both lists.
ECDSA_256r1_Big_Crt_Sig: str = "2F" # ECDSA secp256r1 sig appears in current list only.
Ed448_Big_Sig: str = '3A' # Ed448 signature appears in both lists.
Ed448_Big_Crt_Sig: str = '3B' # Ed448 signature appears in current list only.
def __iter__(self):
return iter(astuple(self))
IdxSigDex = IndexedSigCodex() # Make instance
@dataclass(frozen=True)
class IndexedCurrentSigCodex:
"""IndexedCurrentSigCodex is codex indexed signature codes for current list.
Only provide defined codes.
Undefined are left out so that inclusion(exclusion) via 'in' operator works.
"""
Ed25519_Crt_Sig: str = 'B' # Ed25519 sig appears in current list only.
ECDSA_256k1_Crt_Sig: str = 'D' # ECDSA secp256k1 sig appears in current list only.
ECDSA_256r1_Crt_Sig: str = "F" # ECDSA secp256r1 sig appears in current list.
Ed448_Crt_Sig: str = '0B' # Ed448 signature appears in current list only.
Ed25519_Big_Crt_Sig: str = '2B' # Ed25519 sig appears in current list only.
ECDSA_256k1_Big_Crt_Sig: str = '2D' # ECDSA secp256k1 sig appears in current list only.
ECDSA_256r1_Big_Crt_Sig: str = "2F" # ECDSA secp256r1 sig appears in current list only.
Ed448_Big_Crt_Sig: str = '3B' # Ed448 signature appears in current list only.
def __iter__(self):
return iter(astuple(self))
IdxCrtSigDex = IndexedCurrentSigCodex() # Make instance
@dataclass(frozen=True)
class IndexedBothSigCodex:
"""IndexedBothSigCodex is codex indexed signature codes for both lists.
Only provide defined codes.
Undefined are left out so that inclusion(exclusion) via 'in' operator works.
"""
Ed25519_Sig: str = 'A' # Ed25519 sig appears same in both lists if any.
ECDSA_256k1_Sig: str = 'C' # ECDSA secp256k1 sig appears same in both lists if any.
ECDSA_256r1_Sig: str = "E" # ECDSA secp256r1 sig appears same in both lists if any.
Ed448_Sig: str = '0A' # Ed448 signature appears in both lists.
Ed25519_Big_Sig: str = '2A' # Ed25519 sig appears in both listsy.
ECDSA_256k1_Big_Sig: str = '2C' # ECDSA secp256k1 sig appears in both lists.
ECDSA_256r1_Big_Sig: str = "2E" # ECDSA secp256r1 sig appears in both lists.
Ed448_Big_Sig: str = '3A' # Ed448 signature appears in both lists.
def __iter__(self):
return iter(astuple(self))
IdxBthSigDex = IndexedBothSigCodex() # Make instance
# namedtuple for size entries in Incexer derivation code tables
# hs is the hard size int number of chars in hard (stable) part of code
# ss is the soft size int number of chars in soft (unstable) part of code
# os is the other size int number of chars in other index part of soft
# ms = ss - os main index size computed
# fs is the full size int number of chars in code plus appended material if any
# ls is the lead size int number of bytes to pre-pad pre-converted raw binary
Xizage = namedtuple("Xizage", "hs ss os fs ls")
class Indexer:
""" Indexer is fully qualified cryptographic material primitive base class for
indexed primitives. In special cases some codes in the Index code table
may be of variable length (i.e. not indexed) when the full size table entry
is None. In that case the index is used instread as the length.
Sub classes are derivation code and key event element context specific.
Includes the following attributes and properties:
Attributes:
Properties:
code is str of stable (hard) part of derivation code
raw (bytes): unqualified crypto material usable for crypto operations
index (int): main index offset into list or length of material
ondex (int | None): other index offset into list or length of material
qb64b (bytes): fully qualified Base64 crypto material
qb64 (str | bytes): fully qualified Base64 crypto material
qb2 (bytes): fully qualified binary crypto material
Hidden:
._code (str): value for .code property
._raw (bytes): value for .raw property
._index (int): value for .index property
._ondex (int): value for .ondex property
._infil is method to compute fully qualified Base64 from .raw and .code
._binfil is method to compute fully qualified Base2 from .raw and .code
._exfil is method to extract .code and .raw from fully qualified Base64
._bexfil is method to extract .code and .raw from fully qualified Base2
"""
# Hards table maps from bytes Base64 first code char to int of hard size, hs,
# (stable) of code. The soft size, ss, (unstable) is always > 0 for Indexer.
Hards = ({chr(c): 1 for c in range(65, 65 + 26)})
Hards.update({chr(c): 1 for c in range(97, 97 + 26)})
Hards.update([('0', 2), ('1', 2), ('2', 2), ('3', 2), ('4', 2)])
# Sizes table maps hs chars of code to Xizage namedtuple of (hs, ss, os, fs, ls)
# where hs is hard size, ss is soft size, os is other index size,
# and fs is full size, ls is lead size.
# where ss includes os, so main index size ms = ss - os
# soft size, ss, should always be > 0 for Indexer
Sizes = {
'A': Xizage(hs=1, ss=1, os=0, fs=88, ls=0),
'B': Xizage(hs=1, ss=1, os=0, fs=88, ls=0),
'C': Xizage(hs=1, ss=1, os=0, fs=88, ls=0),
'D': Xizage(hs=1, ss=1, os=0, fs=88, ls=0),
'E': Xizage(hs=1, ss=1, os=0, fs=88, ls=0),
'F': Xizage(hs=1, ss=1, os=0, fs=88, ls=0),
'0A': Xizage(hs=2, ss=2, os=1, fs=156, ls=0),
'0B': Xizage(hs=2, ss=2, os=1, fs=156, ls=0),
'2A': Xizage(hs=2, ss=4, os=2, fs=92, ls=0),
'2B': Xizage(hs=2, ss=4, os=2, fs=92, ls=0),
'2C': Xizage(hs=2, ss=4, os=2, fs=92, ls=0),
'2D': Xizage(hs=2, ss=4, os=2, fs=92, ls=0),
'2E': Xizage(hs=2, ss=4, os=2, fs=92, ls=0),
'2F': Xizage(hs=2, ss=4, os=2, fs=92, ls=0),
'3A': Xizage(hs=2, ss=6, os=3, fs=160, ls=0),
'3B': Xizage(hs=2, ss=6, os=3, fs=160, ls=0),
'0z': Xizage(hs=2, ss=2, os=0, fs=None, ls=0),
'1z': Xizage(hs=2, ss=2, os=1, fs=76, ls=1),
'4z': Xizage(hs=2, ss=6, os=3, fs=80, ls=1),
}
# Bards table maps to hard size, hs, of code from bytes holding sextets
# converted from first code char. Used for ._bexfil.
Bards = ({codeB64ToB2(c): hs for c, hs in Hards.items()})
Codes = asdict(IdrDex) # map code name to code
Names = {val : key for key, val in Codes.items()} # invert map code to code name
def __init__(self, raw=None, code=IdrDex.Ed25519_Sig, index=0, ondex=None,
qb64b=None, qb64=None, qb2=None, strip=False):
"""
Validate as fully qualified
Parameters:
raw (bytes): unqualified crypto material usable for crypto operations
code is str of stable (hard) part of derivation code
index (int): main index offset into list or length of material
ondex (int | None): other index offset into list or length of material
qb64b (bytes): fully qualified Base64 crypto material
qb64 (str | bytes): fully qualified Base64 crypto material
qb2 (bytes): fully qualified binary crypto material
strip (bool): True means strip counter contents from input stream
bytearray after parsing qb64b or qb2. False means do not strip
Needs either (raw and code and index) or qb64b or qb64 or qb2
Otherwise raises EmptyMaterialError
When raw and code provided then validate that code is correct
for length of raw and assign .raw
Else when qb64b or qb64 or qb2 provided extract and assign
.raw, .code, .index, .ondex.
"""
if raw is not None: # raw provided
if not code:
raise EmptyMaterialError("Improper initialization need either "
"(raw and code) or qb64b or qb64 or qb2.")
if not isinstance(raw, (bytes, bytearray)):
raise TypeError(f"Not a bytes or bytearray, raw={raw}.")
if code not in self.Sizes:
raise UnexpectedCodeError(f"Unsupported code={code}.")
hs, ss, os, fs, ls = self.Sizes[code] # get sizes for code
cs = hs + ss # both hard + soft code size
ms = ss - os
if not isinstance(index, int) or index < 0 or index > (64 ** ms - 1):
raise InvalidVarIndexError(f"Invalid index={index} for code={code}.")
if isinstance(ondex, int) and os and not (ondex >= 0 and ondex <= (64 ** os - 1)):
raise InvalidVarIndexError(f"Invalid ondex={ondex} for code={code}.")
if code in IdxCrtSigDex and ondex is not None:
raise InvalidVarIndexError(f"Non None ondex={ondex} for code={code}.")
if code in IdxBthSigDex:
if ondex is None: # set default
ondex = index # when not provided make ondex match index
else:
if ondex != index and os == 0: # must match if os == 0
raise InvalidVarIndexError(f"Non matching ondex={ondex}"
f" and index={index} for "
f"code={code}.")
if not fs: # compute fs from index
if cs % 4:
raise InvalidCodeSizeError(f"Whole code size not multiple of 4 for "
f"variable length material. cs={cs}.")
if os != 0:
raise InvalidCodeSizeError(f"Non-zero other index size for "
f"variable length material. os={os}.")
fs = (index * 4) + cs
rawsize = (fs - cs) * 3 // 4
raw = raw[:rawsize] # copy rawsize from stream, may be less
if len(raw) != rawsize: # forbids shorter
raise RawMaterialError(f"Not enougth raw bytes for code={code}"
f"and index={index} ,expected {rawsize} "
f"got {len(raw)}.")
self._code = code
self._index = index
self._ondex = ondex
self._raw = bytes(raw) # crypto ops require bytes not bytearray
elif qb64b is not None:
self._exfil(qb64b)
if strip: # assumes bytearray
del qb64b[:len(self.qb64b)] # may be variable length fs
elif qb64 is not None:
self._exfil(qb64)
elif qb2 is not None:
self._bexfil(qb2)
if strip: # assumes bytearray
del qb2[:len(self.qb2)] # may be variable length fs
else:
raise EmptyMaterialError("Improper initialization need either "
"(raw and code and index) or qb64b or "
"qb64 or qb2.")
@classmethod
def _rawSize(cls, code):
"""
Returns expected raw size in bytes for a given code. Not applicable to
codes with fs = None
"""
hs, ss, os, fs, ls = cls.Sizes[code] # get sizes
return ((fs - (hs + ss)) * 3 // 4)
@property
def code(self):
"""
Returns ._code
Makes .code read only
"""
return self._code
@property
def name(self):
"""
Returns:
name (str): code name for self.code. Used for annotation for
primitives like Matter
"""
return self.Names[self.code]
@property
def raw(self):
"""
Returns ._raw
Makes .raw read only
"""
return self._raw
@property
def index(self):
"""
Returns ._index
Makes .index read only
"""
return self._index
@property
def ondex(self):
"""
Returns ._ondex
Makes .ondex read only
"""
return self._ondex
@property
def qb64b(self):
"""
Property qb64b:
Returns Fully Qualified Base64 Version encoded as bytes
Assumes self.raw and self.code are correctly populated
"""
return self._infil()
@property
def qb64(self):
"""
Property qb64:
Returns Fully Qualified Base64 Version
Assumes self.raw and self.code are correctly populated
"""
return self.qb64b.decode("utf-8")
@property
def qb2(self):
"""
Property qb2:
Returns Fully Qualified Binary Version Bytes
"""
return self._binfil()
def _infil(self):
"""
Returns fully qualified attached sig base64 bytes computed from
self.raw, self.code and self.index.
cs = hs + ss
os = ss - ms (main index size)
when fs None then size computed & fs = size * 4 + cs
"""
code = self.code # codex value chars hard code
index = self.index # main index value
ondex = self.ondex # other index value
raw = self.raw # bytes or bytearray
ps = (3 - (len(raw) % 3)) % 3 # if lead then same pad size chars & lead size bytes
hs, ss, os, fs, ls = self.Sizes[code]
cs = hs + ss
ms = ss - os
if not fs: # compute fs from index
if cs % 4:
raise InvalidCodeSizeError(f"Whole code size not multiple of 4 for "
f"variable length material. cs={cs}.")
if os != 0:
raise InvalidCodeSizeError(f"Non-zero other index size for "
f"variable length material. os={os}.")
fs = (index * 4) + cs
if index < 0 or index > (64 ** ms - 1):
raise InvalidVarIndexError(f"Invalid index={index} for code={code}.")
if (isinstance(ondex, int) and os and
not (ondex >= 0 and ondex <= (64 ** os - 1))):
raise InvalidVarIndexError(f"Invalid ondex={ondex} for os={os} and "
f"code={code}.")
# both is hard code + converted index + converted ondex
both = (f"{code}{intToB64(index, l=ms)}"
f"{intToB64(ondex if ondex is not None else 0, l=os)}")
# check valid pad size for whole code size, assumes ls is zero
if len(both) != cs:
raise InvalidCodeSizeError("Mismatch code size = {} with table = {}."
.format(cs, len(both)))
if (cs % 4) != ps - ls: # adjusted pad given lead bytes
raise InvalidCodeSizeError(f"Invalid code={both} for converted"
f" raw pad size={ps}.")
# prepend pad bytes, convert, then replace pad chars with full derivation
# code including index,
full = both.encode("utf-8") + encodeB64(bytes([0] * ps) + raw)[ps - ls:]
if len(full) != fs: # invalid size
raise InvalidCodeSizeError(f"Invalid code={both} for raw size={len(raw)}.")
return full
def _binfil(self):
"""
Returns bytes of fully qualified base2 bytes, that is .qb2
self.code and self.index converted to Base2 + self.raw left shifted
with pad bits equivalent of Base64 decode of .qb64 into .qb2
"""
code = self.code # codex chars hard code
index = self.index # main index value
ondex = self.ondex # other index value
raw = self.raw # bytes or bytearray
ps = (3 - (len(raw) % 3)) % 3 # same pad size chars & lead size bytes
hs, ss, os, fs, ls = self.Sizes[code]
cs = hs + ss
ms = ss - os
if index < 0 or index > (64 ** ss - 1):
raise InvalidVarIndexError(f"Invalid index={index} for code={code}.")
if (isinstance(ondex, int) and os and
not (ondex >= 0 and ondex <= (64 ** os - 1))):
raise InvalidVarIndexError(f"Invalid ondex={ondex} for os={os} and "
f"code={code}.")
if not fs: # compute fs from index
if cs % 4:
raise InvalidCodeSizeError(f"Whole code size not multiple of 4 for "
f"variable length material. cs={cs}.")
if os != 0:
raise InvalidCodeSizeError(f"Non-zero other index size for "
f"variable length material. os={os}.")
fs = (index * 4) + cs
# both is hard code + converted index
both = (f"{code}{intToB64(index, l=ms)}"
f"{intToB64(ondex if ondex is not None else 0, l=os)}")
if len(both) != cs:
raise InvalidCodeSizeError("Mismatch code size = {} with table = {}."
.format(cs, len(both)))
if (cs % 4) != ps - ls: # adjusted pad given lead bytes
raise InvalidCodeSizeError(f"Invalid code={both} for converted"
f" raw pad size={ps}.")
n = sceil(cs * 3 / 4) # number of b2 bytes to hold b64 code + index
# convert code both to right align b2 int then left shift in pad bits
# then convert to bytes
bcode = (b64ToInt(both) << (2 * (ps - ls))).to_bytes(n, 'big')
full = bcode + bytes([0] * ls) + raw
bfs = len(full) # binary full size
if bfs % 3 or (bfs * 4 // 3) != fs: # invalid size
raise InvalidCodeSizeError(f"Invalid code={both} for raw size={len(raw)}.")
return full
def _exfil(self, qb64b):
"""
Extracts self.code, self.index, and self.raw from qualified base64 bytes qb64b
cs = hs + ss
ms = ss - os (main index size)
when fs None then size computed & fs = size * 4 + cs
"""
if not qb64b: # empty need more bytes
raise ShortageError("Empty material.")
first = qb64b[:1] # extract first char code selector
if hasattr(first, "decode"):
first = first.decode("utf-8")
if first not in self.Hards:
if first[0] == '-':
raise UnexpectedCountCodeError("Unexpected count code start"
"while extracing Indexer.")
elif first[0] == '_':
raise UnexpectedOpCodeError("Unexpected op code start"
"while extracing Indexer.")
else:
raise UnexpectedCodeError(f"Unsupported code start char={first}.")
hs = self.Hards[first] # get hard code size
if len(qb64b) < hs: # need more bytes
raise ShortageError(f"Need {hs - len(qb64b)} more characters.")
hard = qb64b[:hs] # get hard code
if hasattr(hard, "decode"):
hard = hard.decode("utf-8")
if hard not in self.Sizes:
raise UnexpectedCodeError(f"Unsupported code ={hard}.")
hs, ss, os, fs, ls = self.Sizes[hard] # assumes hs in both tables consistent
cs = hs + ss # both hard + soft code size
ms = ss - os
# assumes that unit tests on Indexer and IndexerCodex ensure that
# .Codes and .Sizes are well formed.
# hs consistent and hs > 0 and ss > 0 and (fs >= hs + ss if fs is not None else True)
# assumes no variable length indexed codes so fs is not None
if len(qb64b) < cs: # need more bytes
raise ShortageError(f"Need {cs - len(qb64b)} more characters.")
index = qb64b[hs:hs+ms] # extract index/size chars
if hasattr(index, "decode"):
index = index.decode("utf-8")
index = b64ToInt(index) # compute int index
ondex = qb64b[hs+ms:hs+ms+os] # extract ondex chars
if hasattr(ondex, "decode"):
ondex = ondex.decode("utf-8")
if hard in IdxCrtSigDex: # if current sig then ondex from code must be 0
ondex = b64ToInt(ondex) if os else None # compute ondex from code
if ondex: # not zero or None so error
raise ValueError(f"Invalid ondex={ondex} for code={hard}.")
else:
ondex = None # zero so set to None when current only
else:
ondex = b64ToInt(ondex) if os else index
# index is index for some codes and variable length for others
if not fs: # compute fs from index which means variable length
if cs % 4:
raise ValidationError(f"Whole code size not multiple of 4 for "
f"variable length material. cs={cs}.")
if os != 0:
raise ValidationError(f"Non-zero other index size for "
f"variable length material. os={os}.")
fs = (index * 4) + cs
if len(qb64b) < fs: # need more bytes
raise ShortageError(f"Need {fs - len(qb64b)} more chars.")
qb64b = qb64b[:fs] # fully qualified primitive code plus material
if hasattr(qb64b, "encode"): # only convert extracted chars from stream
qb64b = qb64b.encode("utf-8")
# strip off prepended code and append pad characters
#ps = cs % 4 # pad size ps = cs mod 4, same pad chars and lead bytes
#base = ps * b'A' + qb64b[cs:] # replace prepend code with prepad zeros
#raw = decodeB64(base)[ps+ls:] # decode and strip off ps+ls prepad bytes
# check for non-zeroed pad bits or lead bytes
ps = cs % 4 # code pad size ps = cs mod 4
pbs = 2 * (ps if ps else ls) # pad bit size in bits
if ps: # ps. IF ps THEN not ls (lead) and vice versa OR not ps and not ls
base = ps * b'A' + qb64b[cs:] # replace pre code with prepad chars of zero
paw = decodeB64(base) # decode base to leave prepadded raw
pi = (int.from_bytes(paw[:ps], "big")) # prepad as int
if pi & (2 ** pbs - 1 ): # masked pad bits non-zero
raise ValueError(f"Non zeroed prepad bits = "
f"{pi & (2 ** pbs - 1 ):<06b} in {qb64b[cs:cs+1]}.")
raw = paw[ps:] # strip off ps prepad paw bytes
else: # not ps. IF not ps THEN may or may not be ls (lead)
base = qb64b[cs:] # strip off code leaving lead chars if any and value
# decode lead chars + val leaving lead bytes + raw bytes
# then strip off ls lead bytes leaving raw
paw = decodeB64(base) # decode base to leave prepadded paw bytes
li = int.from_bytes(paw[:ls], "big") # lead as int
if li: # pre pad lead bytes must be zero
if ls == 1:
raise ValueError(f"Non zeroed lead byte = 0x{li:02x}.")
else:
raise ValueError(f"Non zeroed lead bytes = 0x{li:04x}.")
raw = paw[ls:]
if len(raw) != (len(qb64b) - cs) * 3 // 4: # exact lengths
raise ConversionError(f"Improperly qualified material = {qb64b}")
self._code = hard
self._index = index
self._ondex = ondex
self._raw = raw # must be bytes for crpto opts and immutable not bytearray
def _bexfil(self, qb2):
"""
Extracts self.code, self.index, and self.raw from qualified base2 bytes qb2
cs = hs + ss
ms = ss - os (main index size)
when fs None then size computed & fs = size * 4 + cs
"""
if not qb2: # empty need more bytes
raise ShortageError("Empty material, Need more bytes.")
first = nabSextets(qb2, 1) # extract first sextet as code selector
if first not in self.Bards:
if first[0] == b'\xf8': # b64ToB2('-')
raise UnexpectedCountCodeError("Unexpected count code start"
"while extracing Matter.")
elif first[0] == b'\xfc': # b64ToB2('_')
raise UnexpectedOpCodeError("Unexpected op code start"
"while extracing Matter.")
else:
raise UnexpectedCodeError(f"Unsupported code start sextet={first}.")
hs = self.Bards[first] # get code hard size equvalent sextets
bhs = sceil(hs * 3 / 4) # bhs is min bytes to hold hs sextets
if len(qb2) < bhs: # need more bytes
raise ShortageError(f"Need {bhs - len(qb2)} more bytes.")
hard = codeB2ToB64(qb2, hs) # extract and convert hard part of code
if hard not in self.Sizes:
raise UnexpectedCodeError(f"Unsupported code ={hard}.")
hs, ss, os, fs, ls = self.Sizes[hard]
cs = hs + ss # both hs and ss
ms = ss - os
# assumes that unit tests on Indexer and IndexerCodex ensure that
# .Codes and .Sizes are well formed.
# hs consistent and hs > 0 and ss > 0 and (fs >= hs + ss if fs is not None else True)
bcs = sceil(cs * 3 / 4) # bcs is min bytes to hold cs sextets
if len(qb2) < bcs: # need more bytes
raise ShortageError("Need {} more bytes.".format(bcs - len(qb2)))
both = codeB2ToB64(qb2, cs) # extract and convert both hard and soft part of code
index = b64ToInt(both[hs:hs+ms]) # compute index
if hard in IdxCrtSigDex: # if current sig then ondex from code must be 0
ondex = b64ToInt(both[hs+ms:hs+ms+os]) if os else None # compute ondex from code
if ondex: # not zero or None so error
raise ValueError(f"Invalid ondex={ondex} for code={hard}.")
else:
ondex = None # zero so set to None when current only
else:
ondex = b64ToInt(both[hs+ms:hs+ms+os]) if os else index
if hard in IdxCrtSigDex: # if current sig then ondex from code must be 0
if ondex: # not zero so error
raise ValueError(f"Invalid ondex={ondex} for code={hard}.")
else: # zero so set to None
ondex = None
if not fs: # compute fs from size chars in ss part of code
if cs % 4:
raise ValidationError(f"Whole code size not multiple of 4 for "
f"variable length material. cs={cs}.")
if os != 0:
raise ValidationError(f"Non-zero other index size for "
f"variable length material. os={os}.")
fs = (index * 4) + cs
bfs = sceil(fs * 3 / 4) # bfs is min bytes to hold fs sextets
if len(qb2) < bfs: # need more bytes
raise ShortageError("Need {} more bytes.".format(bfs - len(qb2)))
qb2 = qb2[:bfs] # extract qb2 fully qualified primitive code plus material
# check for non-zeroed prepad bits or lead bytes
ps = cs % 4 # code pad size ps = cs mod 4
pbs = 2 * (ps if ps else ls) # pad bit size in bits
if ps: # ps. IF ps THEN not ls (lead) and vice versa OR not ps and not ls
# convert last byte of code bytes in which are pad bits to int
pi = (int.from_bytes(qb2[bcs-1:bcs], "big"))
if pi & (2 ** pbs - 1 ): # masked pad bits non-zero
raise ValueError(f"Non zeroed pad bits = "
f"{pi & (2 ** pbs - 1 ):>08b} in 0x{pi:02x}.")
else: # not ps. IF not ps THEN may or may not be ls (lead)
li = int.from_bytes(qb2[bcs:bcs+ls], "big") # lead as int
if li: # pre pad lead bytes must be zero
if ls == 1:
raise ValueError(f"Non zeroed lead byte = 0x{li:02x}.")
else:
raise ValueError(f"Non zeroed lead bytes = 0x{li:02x}.")
raw = qb2[(bcs + ls):] # strip code and leader bytes from qb2 to get raw
if len(raw) != (len(qb2) - bcs - ls): # exact lengths
raise ConversionError(f"Improperly qualified material = {qb2}")
self._code = hard
self._index = index
self._ondex = ondex
self._raw = bytes(raw) # must be bytes for crypto ops and not bytearray mutable
class Siger(Indexer):
"""
Siger is subclass of Indexer, indexed signature material,
Adds .verfer property which is instance of Verfer that provides
associated signature verifier.
See Indexer for inherited attributes and properties:
Attributes:
Properties:
verfer (Verfer): instance if any provides public verification key
Methods:
Hidden:
_verfer (Verfer): value for .verfer property
"""
def __init__(self, verfer=None, **kwa):
"""Initialze instance
Parameters: See Matter for inherted parameters
verfer (Verfer): instance if any provides public verification key
"""
super(Siger, self).__init__(**kwa)
if self.code not in IdxSigDex:
raise ValidationError("Invalid code = {} for Siger."
"".format(self.code))
self.verfer = verfer
@property
def verfer(self):
"""
Property verfer:
Returns Verfer instance
Assumes ._verfer is correctly assigned
"""
return self._verfer
@verfer.setter
def verfer(self, verfer):
""" verfer property setter """
self._verfer = verfer