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.
8Data layout for reference.
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 |
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"""
25import os
26import struct
27import typing as typ
29from . import kdf
30from . import primes
32SBK_VERSION_V1 = 1
34DEFAULT_RAW_SALT_LEN = 13
35DEFAULT_BRAINKEY_LEN = 6
37ENV_RAW_SALT_LEN = os.getenv('SBK_DEBUG_RAW_SALT_LEN')
38ENV_BRAINKEY_LEN = os.getenv('SBK_DEBUG_BRAINKEY_LEN')
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
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
47PARAM_CFG_LEN = 3
48SHARE_X_COORD_LEN = 1
49SALT_LEN = PARAM_CFG_LEN + RAW_SALT_LEN
51MASTER_KEY_LEN = RAW_SALT_LEN + BRAINKEY_LEN
53SHARE_LEN = PARAM_CFG_LEN + SHARE_X_COORD_LEN + RAW_SALT_LEN + BRAINKEY_LEN
55MIN_ENTROPY = int(os.getenv('SBK_MIN_ENTROPY' , "16"))
56MAX_ENTROPY_WAIT = int(os.getenv('SBK_MAX_ENTROPY_WAIT', "10"))
58DEFAULT_KDF_TARGET_DURATION = int(os.getenv('SBK_KDF_TARGET_DURATION', "90"))
60DEFAULT_THRESHOLD = int(os.getenv('SBK_THRESHOLD' , "3"))
61DEFAULT_NUM_SHARES = int(os.getenv('SBK_NUM_SHARES', "5"))
63# constrained by f_threshold (4bits)
64MAX_THRESHOLD = 16
67class ParamConfig(typ.NamedTuple):
69 version : int
70 threshold : int
71 num_shares: int
72 kdf_params: kdf.KDFParams
74 @property
75 def prime(self) -> int:
76 master_key_bits = MASTER_KEY_LEN * 8
77 return primes.get_pow2prime(master_key_bits)
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
87 if threshold > _num_shares:
88 errmsg = f"threshold must be <= num_shares, got {threshold} > {_num_shares}"
89 raise ValueError(errmsg)
91 if not 1 <= threshold <= MAX_THRESHOLD:
92 errmsg = f"Invalid threshold {threshold}"
93 raise ValueError(errmsg)
95 param_cfg = ParamConfig(
96 version=SBK_VERSION_V1,
97 threshold=threshold,
98 num_shares=_num_shares,
99 kdf_params=kdf_params,
100 )
102 return param_cfg
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)
111 # B: Unsigned Char (1 byte)
112 # H: Unsigned Short (2 bytes)
113 fields_01, fields_234 = struct.unpack("!BH", data[:3])
115 # We don't include the share_no in the ParamConfig, it
116 # is decoded separately for each share.
117 # share_no = _fields_5
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}")
124 threshold = f_threshold + 1
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
131 kdf_params = kdf.KDFParams.decode(fields_234)
132 return ParamConfig(version, threshold, num_shares, kdf_params)
135def param_cfg2bytes(param_cfg: ParamConfig) -> bytes:
136 """Serialize ParamConfig.
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
144 fields_01 = 0
145 fields_01 |= param_cfg.version << 4
146 fields_01 |= f_threshold
148 fields_234 = param_cfg.kdf_params.encode()
149 param_cfg_data = struct.pack("!BH", fields_01, fields_234)
150 return param_cfg_data