Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# This file is part of the sbk project 

2# https://github.com/mbarkhau/sbk 

3# 

4# Copyright (c) 2019-2021 Manuel Barkhau (mbarkhau@gmail.com) - MIT License 

5# SPDX-License-Identifier: MIT 

6"""Parameter encoding/decoding/initialization. 

7 

8Data layout for reference. 

9 

10| Field | Size | Info | 

11| ------------------- | ------ | -------------------------------------------------------- | 

12| `f_version` | 4 bit | ... | 

13| `f_threshold` | 4 bit | minimum shares required for recovery | 

14| | | max: 1..2**4 = 1..16 | 

15| `f_share_no` | 8 bit | For shares, the x-coordinate > 0 | 

16| `f_kdf_parallelism` | 4 bit | `ceil(2 ** n) = kdf_parallelism` in number of threads | 

17| `f_kdf_mem_cost` | 6 bit | `ceil(1.25 ** n) = kdf_mem_cost` in MiB | 

18| `f_kdf_time_cost` | 6 bit | `ceil(1.5 ** n) = kdf_time_cost` in iterations | 

19 

200 3 4 7 8 11 12 17 18 23 24 31 

21[ ver ] [thres] [kdf_p] [ kdf_mem ] [ kdf_time] [ share_no ] 

22 4bit 4bit 4bit 6bit 6bit 8bit 

23""" 

24 

25import os 

26import struct 

27import typing as typ 

28 

29from . import kdf 

30from . import primes 

31 

32SBK_VERSION_V1 = 1 

33 

34DEFAULT_RAW_SALT_LEN = 13 

35DEFAULT_BRAINKEY_LEN = 6 

36 

37ENV_RAW_SALT_LEN = os.getenv('SBK_DEBUG_RAW_SALT_LEN') 

38ENV_BRAINKEY_LEN = os.getenv('SBK_DEBUG_BRAINKEY_LEN') 

39 

40RAW_SALT_LEN = int(ENV_RAW_SALT_LEN) if ENV_RAW_SALT_LEN else DEFAULT_RAW_SALT_LEN 

41BRAINKEY_LEN = int(ENV_BRAINKEY_LEN) if ENV_BRAINKEY_LEN else DEFAULT_BRAINKEY_LEN 

42 

43# linear fit evaluated with: python -m sbk.ui_common 

44RAW_SALT_MIN_ENTROPY = RAW_SALT_LEN * 0.19 + 0.3 

45BRAINKEY_MIN_ENTROPY = BRAINKEY_LEN * 0.19 + 0.3 

46 

47PARAM_CFG_LEN = 3 

48SHARE_X_COORD_LEN = 1 

49SALT_LEN = PARAM_CFG_LEN + RAW_SALT_LEN 

50 

51MASTER_KEY_LEN = RAW_SALT_LEN + BRAINKEY_LEN 

52 

53SHARE_LEN = PARAM_CFG_LEN + SHARE_X_COORD_LEN + RAW_SALT_LEN + BRAINKEY_LEN 

54 

55MIN_ENTROPY = int(os.getenv('SBK_MIN_ENTROPY' , "16")) 

56MAX_ENTROPY_WAIT = int(os.getenv('SBK_MAX_ENTROPY_WAIT', "10")) 

57 

58DEFAULT_KDF_TARGET_DURATION = int(os.getenv('SBK_KDF_TARGET_DURATION', "90")) 

59 

60DEFAULT_THRESHOLD = int(os.getenv('SBK_THRESHOLD' , "3")) 

61DEFAULT_NUM_SHARES = int(os.getenv('SBK_NUM_SHARES', "5")) 

62 

63# constrained by f_threshold (4bits) 

64MAX_THRESHOLD = 16 

65 

66 

67class ParamConfig(typ.NamedTuple): 

68 

69 version : int 

70 threshold : int 

71 num_shares: int 

72 kdf_params: kdf.KDFParams 

73 

74 @property 

75 def prime(self) -> int: 

76 master_key_bits = MASTER_KEY_LEN * 8 

77 return primes.get_pow2prime(master_key_bits) 

78 

79 

80def init_param_config( 

81 kdf_params: kdf.KDFParams, 

82 threshold : int, 

83 num_shares: typ.Optional[int] = None, 

84) -> ParamConfig: 

85 _num_shares = threshold if num_shares is None else num_shares 

86 

87 if threshold > _num_shares: 

88 errmsg = f"threshold must be <= num_shares, got {threshold} > {_num_shares}" 

89 raise ValueError(errmsg) 

90 

91 if not 1 <= threshold <= MAX_THRESHOLD: 

92 errmsg = f"Invalid threshold {threshold}" 

93 raise ValueError(errmsg) 

94 

95 param_cfg = ParamConfig( 

96 version=SBK_VERSION_V1, 

97 threshold=threshold, 

98 num_shares=_num_shares, 

99 kdf_params=kdf_params, 

100 ) 

101 

102 return param_cfg 

103 

104 

105def bytes2param_cfg(data: bytes) -> ParamConfig: 

106 """Deserialize ParamConfig from the Salt or a Share.""" 

107 if len(data) < 3: 

108 errmsg = f"Invalid params len={len(data)}" 

109 raise ValueError(errmsg) 

110 

111 # B: Unsigned Char (1 byte) 

112 # H: Unsigned Short (2 bytes) 

113 fields_01, fields_234 = struct.unpack("!BH", data[:3]) 

114 

115 # We don't include the share_no in the ParamConfig, it 

116 # is decoded separately for each share. 

117 # share_no = _fields_5 

118 

119 version = (fields_01 >> 4) & 0xF 

120 f_threshold = (fields_01 >> 0) & 0xF 

121 if version != SBK_VERSION_V1: 

122 raise ValueError(f"Unsupported Version {version}") 

123 

124 threshold = f_threshold + 1 

125 

126 # The param_cfg encoding doesn't include num_shares as it's 

127 # only required when originally generating the shares. The 

128 # minimum value is threshold, so that is what we set it to. 

129 num_shares = threshold 

130 

131 kdf_params = kdf.KDFParams.decode(fields_234) 

132 return ParamConfig(version, threshold, num_shares, kdf_params) 

133 

134 

135def param_cfg2bytes(param_cfg: ParamConfig) -> bytes: 

136 """Serialize ParamConfig. 

137 

138 Since these fields are part of the salt, we try 

139 to keep the serialized param_cfg small and leave 

140 more room for randomness, hence the bit twiddling. 

141 """ 

142 f_threshold = param_cfg.threshold - 1 

143 

144 fields_01 = 0 

145 fields_01 |= param_cfg.version << 4 

146 fields_01 |= f_threshold 

147 

148 fields_234 = param_cfg.kdf_params.encode() 

149 param_cfg_data = struct.pack("!BH", fields_01, fields_234) 

150 return param_cfg_data