pyquorum 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyquorum-0.1.3/LICENSE +21 -0
- pyquorum-0.1.3/PKG-INFO +76 -0
- pyquorum-0.1.3/README.md +63 -0
- pyquorum-0.1.3/pyproject.toml +34 -0
- pyquorum-0.1.3/src/pyquorum/__init__.py +4 -0
- pyquorum-0.1.3/src/pyquorum/_cory.pyi +3 -0
- pyquorum-0.1.3/src/pyquorum/exceptions.py +19 -0
- pyquorum-0.1.3/src/pyquorum/keys/__init__.py +0 -0
- pyquorum-0.1.3/src/pyquorum/keys/generate.py +15 -0
- pyquorum-0.1.3/src/pyquorum/py.typed +0 -0
- pyquorum-0.1.3/src/pyquorum/sharing/__init__.py +0 -0
- pyquorum-0.1.3/src/pyquorum/sharing/base.py +20 -0
- pyquorum-0.1.3/src/pyquorum/sharing/shamir.py +76 -0
- pyquorum-0.1.3/src/rust/Cargo.lock +309 -0
- pyquorum-0.1.3/src/rust/Cargo.toml +18 -0
- pyquorum-0.1.3/src/rust/src/lib.rs +184 -0
pyquorum-0.1.3/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 svvqt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pyquorum-0.1.3/PKG-INFO
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyquorum
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Classifier: Programming Language :: Python :: 3
|
|
5
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
6
|
+
Classifier: Topic :: Security :: Cryptography
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Summary: Cryptographic library for secret sharing and key management, powered by Rust
|
|
9
|
+
Author-email: svvqt <kon.vitkovskii@gmail.com>
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
12
|
+
Project-URL: Repository, https://github.com/svvqt/pyquorum
|
|
13
|
+
|
|
14
|
+
# PyQuorum
|
|
15
|
+
|
|
16
|
+
Cryptographic library for secret sharing and key management, powered by Rust
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
```bash
|
|
20
|
+
pip install pyquorum
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
```python
|
|
25
|
+
from pyquorum import ShamirScheme, generate_key
|
|
26
|
+
|
|
27
|
+
k=3 # threshold for combine
|
|
28
|
+
n=4 # number of total shares
|
|
29
|
+
key = generate_key()
|
|
30
|
+
Scheme = ShamirScheme(k, n)
|
|
31
|
+
shares = Scheme.split(key)
|
|
32
|
+
combine = Scheme.combine(shares)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Security
|
|
36
|
+
This library does:
|
|
37
|
+
- generate key
|
|
38
|
+
- split secret
|
|
39
|
+
- combine secret
|
|
40
|
+
|
|
41
|
+
What it doesn't:
|
|
42
|
+
- replace encryption packages like cryptography, pyserpent and etc
|
|
43
|
+
|
|
44
|
+
If you found a security issue, please refer to [SECURITY.md](SECURITY.md)
|
|
45
|
+
|
|
46
|
+
## Roadmap to v1.0.0
|
|
47
|
+
- [x] - Shamir Scheme
|
|
48
|
+
- [ ] - Blakley Scheme
|
|
49
|
+
- [ ] - Additive Scheme
|
|
50
|
+
- [ ] - HKDF Key Derivation
|
|
51
|
+
- [ ] - Threshold ECDSA
|
|
52
|
+
|
|
53
|
+
## Support
|
|
54
|
+
If you find this package usefull, you can star repo on github
|
|
55
|
+
|
|
56
|
+
## Theory
|
|
57
|
+
|
|
58
|
+
### Shamir Scheme Share
|
|
59
|
+
|
|
60
|
+
How to split a secret key
|
|
61
|
+
|
|
62
|
+

|
|
63
|
+
|
|
64
|
+
How to combine a secret key
|
|
65
|
+
|
|
66
|
+

|
|
67
|
+
|
|
68
|
+
### Example
|
|
69
|
+
|
|
70
|
+

|
|
71
|
+
|
|
72
|
+

|
|
73
|
+
|
|
74
|
+

|
|
75
|
+
|
|
76
|
+

|
pyquorum-0.1.3/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# PyQuorum
|
|
2
|
+
|
|
3
|
+
Cryptographic library for secret sharing and key management, powered by Rust
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
```bash
|
|
7
|
+
pip install pyquorum
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Quick Start
|
|
11
|
+
```python
|
|
12
|
+
from pyquorum import ShamirScheme, generate_key
|
|
13
|
+
|
|
14
|
+
k=3 # threshold for combine
|
|
15
|
+
n=4 # number of total shares
|
|
16
|
+
key = generate_key()
|
|
17
|
+
Scheme = ShamirScheme(k, n)
|
|
18
|
+
shares = Scheme.split(key)
|
|
19
|
+
combine = Scheme.combine(shares)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Security
|
|
23
|
+
This library does:
|
|
24
|
+
- generate key
|
|
25
|
+
- split secret
|
|
26
|
+
- combine secret
|
|
27
|
+
|
|
28
|
+
What it doesn't:
|
|
29
|
+
- replace encryption packages like cryptography, pyserpent and etc
|
|
30
|
+
|
|
31
|
+
If you found a security issue, please refer to [SECURITY.md](SECURITY.md)
|
|
32
|
+
|
|
33
|
+
## Roadmap to v1.0.0
|
|
34
|
+
- [x] - Shamir Scheme
|
|
35
|
+
- [ ] - Blakley Scheme
|
|
36
|
+
- [ ] - Additive Scheme
|
|
37
|
+
- [ ] - HKDF Key Derivation
|
|
38
|
+
- [ ] - Threshold ECDSA
|
|
39
|
+
|
|
40
|
+
## Support
|
|
41
|
+
If you find this package usefull, you can star repo on github
|
|
42
|
+
|
|
43
|
+
## Theory
|
|
44
|
+
|
|
45
|
+
### Shamir Scheme Share
|
|
46
|
+
|
|
47
|
+
How to split a secret key
|
|
48
|
+
|
|
49
|
+

|
|
50
|
+
|
|
51
|
+
How to combine a secret key
|
|
52
|
+
|
|
53
|
+

|
|
54
|
+
|
|
55
|
+
### Example
|
|
56
|
+
|
|
57
|
+

|
|
58
|
+
|
|
59
|
+

|
|
60
|
+
|
|
61
|
+

|
|
62
|
+
|
|
63
|
+

|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["maturin>=1.0,<2.0"]
|
|
3
|
+
build-backend = "maturin"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pyquorum"
|
|
7
|
+
version = "0.1.3"
|
|
8
|
+
description = "Cryptographic library for secret sharing and key management, powered by Rust"
|
|
9
|
+
license = {file = "LICENSE"}
|
|
10
|
+
authors = [
|
|
11
|
+
{name = "svvqt", email = "kon.vitkovskii@gmail.com"}
|
|
12
|
+
]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Topic :: Security :: Cryptography",
|
|
17
|
+
]
|
|
18
|
+
dependencies = []
|
|
19
|
+
requires-python = ">=3.9"
|
|
20
|
+
readme = "README.md"
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Repository = "https://github.com/svvqt/pyquorum"
|
|
24
|
+
|
|
25
|
+
[tool.maturin]
|
|
26
|
+
python-source = "src"
|
|
27
|
+
module-name = "pyquorum.pyquorum_core"
|
|
28
|
+
features = ["pyo3/abi3-py39"]
|
|
29
|
+
include = ["LICENSE", "README.md"]
|
|
30
|
+
python-package = ["pyquorum"]
|
|
31
|
+
manifest-path = "src/rust/Cargo.toml"
|
|
32
|
+
|
|
33
|
+
[tool.pytest.ini_options]
|
|
34
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class PyQuorumError(Exception):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
class InvalidKeyError(PyQuorumError):
|
|
5
|
+
pass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class InvalidShareError(PyQuorumError):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
class ThresholdError(PyQuorumError):
|
|
12
|
+
def __init__(self, k, n):
|
|
13
|
+
self.k = k
|
|
14
|
+
self.n = n
|
|
15
|
+
def __str__(self):
|
|
16
|
+
return f"K must be less then N, now k={self.k}, n={self.n}"
|
|
17
|
+
|
|
18
|
+
class GenerateKeyError(PyQuorumError):
|
|
19
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .. import pyquorum_core
|
|
2
|
+
from ..exceptions import GenerateKeyError
|
|
3
|
+
|
|
4
|
+
def generate_key() -> bytes:
|
|
5
|
+
"""Generate a random secret key
|
|
6
|
+
|
|
7
|
+
Returns
|
|
8
|
+
--------
|
|
9
|
+
bytes:
|
|
10
|
+
32 byte generated key
|
|
11
|
+
"""
|
|
12
|
+
try:
|
|
13
|
+
return pyquorum_core.generate_key()
|
|
14
|
+
except ValueError as e:
|
|
15
|
+
raise GenerateKeyError(str(e))
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from ..exceptions import ThresholdError
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Scheme(ABC):
|
|
6
|
+
def __init__(self, k: int, n: int):
|
|
7
|
+
self.k = k
|
|
8
|
+
self.n = n
|
|
9
|
+
if self.k > self.n:
|
|
10
|
+
raise ThresholdError(self.k, self.n)
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def split(self, key: bytes) -> list[str]:
|
|
14
|
+
"""Разделение ключа"""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def combine(self, shares: list[str]) -> bytes:
|
|
19
|
+
"""Соединение ключа"""
|
|
20
|
+
pass
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from .. import pyquorum_core
|
|
2
|
+
from .base import Scheme
|
|
3
|
+
from ..exceptions import InvalidKeyError, InvalidShareError
|
|
4
|
+
|
|
5
|
+
class ShamirScheme(Scheme):
|
|
6
|
+
"""
|
|
7
|
+
Class for shamir secret sharing
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
k: int
|
|
13
|
+
minimum number of shares required to reconstruct the secret
|
|
14
|
+
n: int
|
|
15
|
+
total number of shares to generate
|
|
16
|
+
"""
|
|
17
|
+
def split(self, key: bytes) -> list[str]:
|
|
18
|
+
"""
|
|
19
|
+
method for splitting secret key to number of shares
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
key: bytes
|
|
24
|
+
secret key
|
|
25
|
+
|
|
26
|
+
Returns
|
|
27
|
+
-------
|
|
28
|
+
shares: list[str]:
|
|
29
|
+
secret key that splitted on shares
|
|
30
|
+
|
|
31
|
+
Raises
|
|
32
|
+
------
|
|
33
|
+
InvalidKeyError
|
|
34
|
+
If key not bytes or not 32 length
|
|
35
|
+
"""
|
|
36
|
+
if not isinstance(key, bytes):
|
|
37
|
+
raise InvalidKeyError(f"Key must be bytes, not {type(key)}")
|
|
38
|
+
|
|
39
|
+
if len(key) != 32:
|
|
40
|
+
raise InvalidKeyError(f"Key length must be 32, not {len(key)}")
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
return pyquorum_core.split_secret(key, self.k, self.n)
|
|
44
|
+
except ValueError as e:
|
|
45
|
+
raise InvalidKeyError(str(e))
|
|
46
|
+
|
|
47
|
+
def combine(self, shares: list[str]) -> bytes:
|
|
48
|
+
"""
|
|
49
|
+
combine shares to secret key
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
shares: list[str]
|
|
54
|
+
splitted pieces of secret key
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
secret key: bytes
|
|
59
|
+
combined secret key
|
|
60
|
+
|
|
61
|
+
Raises
|
|
62
|
+
------
|
|
63
|
+
InvalidShareError
|
|
64
|
+
If shares not string or less then k
|
|
65
|
+
"""
|
|
66
|
+
for share in shares:
|
|
67
|
+
if not isinstance(share, str):
|
|
68
|
+
raise InvalidShareError(f"Share must be String, not {type(share)}")
|
|
69
|
+
|
|
70
|
+
if len(shares) < self.k:
|
|
71
|
+
raise InvalidShareError(f"Need at least {self.k} shares")
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
return pyquorum_core.combine_shares(shares, self.k)
|
|
75
|
+
except ValueError as e:
|
|
76
|
+
raise InvalidShareError(str(e))
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "block-buffer"
|
|
7
|
+
version = "0.10.4"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"generic-array",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[[package]]
|
|
15
|
+
name = "cfg-if"
|
|
16
|
+
version = "1.0.4"
|
|
17
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
18
|
+
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
19
|
+
|
|
20
|
+
[[package]]
|
|
21
|
+
name = "cpufeatures"
|
|
22
|
+
version = "0.2.17"
|
|
23
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
24
|
+
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
|
25
|
+
dependencies = [
|
|
26
|
+
"libc",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[[package]]
|
|
30
|
+
name = "crypto-common"
|
|
31
|
+
version = "0.1.7"
|
|
32
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
33
|
+
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
|
34
|
+
dependencies = [
|
|
35
|
+
"generic-array",
|
|
36
|
+
"typenum",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[[package]]
|
|
40
|
+
name = "digest"
|
|
41
|
+
version = "0.10.7"
|
|
42
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
43
|
+
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
|
44
|
+
dependencies = [
|
|
45
|
+
"block-buffer",
|
|
46
|
+
"crypto-common",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[[package]]
|
|
50
|
+
name = "generic-array"
|
|
51
|
+
version = "0.14.7"
|
|
52
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
53
|
+
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
|
54
|
+
dependencies = [
|
|
55
|
+
"typenum",
|
|
56
|
+
"version_check",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[[package]]
|
|
60
|
+
name = "getrandom"
|
|
61
|
+
version = "0.2.17"
|
|
62
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
63
|
+
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
|
64
|
+
dependencies = [
|
|
65
|
+
"cfg-if",
|
|
66
|
+
"libc",
|
|
67
|
+
"wasi",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
[[package]]
|
|
71
|
+
name = "heck"
|
|
72
|
+
version = "0.5.0"
|
|
73
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
74
|
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
75
|
+
|
|
76
|
+
[[package]]
|
|
77
|
+
name = "libc"
|
|
78
|
+
version = "0.2.185"
|
|
79
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
80
|
+
checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
|
|
81
|
+
|
|
82
|
+
[[package]]
|
|
83
|
+
name = "once_cell"
|
|
84
|
+
version = "1.21.4"
|
|
85
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
86
|
+
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
87
|
+
|
|
88
|
+
[[package]]
|
|
89
|
+
name = "portable-atomic"
|
|
90
|
+
version = "1.13.1"
|
|
91
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
92
|
+
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
|
93
|
+
|
|
94
|
+
[[package]]
|
|
95
|
+
name = "ppv-lite86"
|
|
96
|
+
version = "0.2.21"
|
|
97
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
98
|
+
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
|
99
|
+
dependencies = [
|
|
100
|
+
"zerocopy",
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
[[package]]
|
|
104
|
+
name = "proc-macro2"
|
|
105
|
+
version = "1.0.106"
|
|
106
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
107
|
+
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
|
108
|
+
dependencies = [
|
|
109
|
+
"unicode-ident",
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
[[package]]
|
|
113
|
+
name = "pyo3"
|
|
114
|
+
version = "0.28.3"
|
|
115
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
116
|
+
checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12"
|
|
117
|
+
dependencies = [
|
|
118
|
+
"libc",
|
|
119
|
+
"once_cell",
|
|
120
|
+
"portable-atomic",
|
|
121
|
+
"pyo3-build-config",
|
|
122
|
+
"pyo3-ffi",
|
|
123
|
+
"pyo3-macros",
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
[[package]]
|
|
127
|
+
name = "pyo3-build-config"
|
|
128
|
+
version = "0.28.3"
|
|
129
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
130
|
+
checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e"
|
|
131
|
+
dependencies = [
|
|
132
|
+
"target-lexicon",
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
[[package]]
|
|
136
|
+
name = "pyo3-ffi"
|
|
137
|
+
version = "0.28.3"
|
|
138
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
139
|
+
checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e"
|
|
140
|
+
dependencies = [
|
|
141
|
+
"libc",
|
|
142
|
+
"pyo3-build-config",
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
[[package]]
|
|
146
|
+
name = "pyo3-macros"
|
|
147
|
+
version = "0.28.3"
|
|
148
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
149
|
+
checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813"
|
|
150
|
+
dependencies = [
|
|
151
|
+
"proc-macro2",
|
|
152
|
+
"pyo3-macros-backend",
|
|
153
|
+
"quote",
|
|
154
|
+
"syn",
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
[[package]]
|
|
158
|
+
name = "pyo3-macros-backend"
|
|
159
|
+
version = "0.28.3"
|
|
160
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
161
|
+
checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb"
|
|
162
|
+
dependencies = [
|
|
163
|
+
"heck",
|
|
164
|
+
"proc-macro2",
|
|
165
|
+
"pyo3-build-config",
|
|
166
|
+
"quote",
|
|
167
|
+
"syn",
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
[[package]]
|
|
171
|
+
name = "pyquorum_core"
|
|
172
|
+
version = "0.1.0"
|
|
173
|
+
dependencies = [
|
|
174
|
+
"pyo3",
|
|
175
|
+
"rand",
|
|
176
|
+
"sha2",
|
|
177
|
+
"zeroize",
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
[[package]]
|
|
181
|
+
name = "quote"
|
|
182
|
+
version = "1.0.45"
|
|
183
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
184
|
+
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
|
185
|
+
dependencies = [
|
|
186
|
+
"proc-macro2",
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
[[package]]
|
|
190
|
+
name = "rand"
|
|
191
|
+
version = "0.8.5"
|
|
192
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
193
|
+
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
|
194
|
+
dependencies = [
|
|
195
|
+
"libc",
|
|
196
|
+
"rand_chacha",
|
|
197
|
+
"rand_core",
|
|
198
|
+
]
|
|
199
|
+
|
|
200
|
+
[[package]]
|
|
201
|
+
name = "rand_chacha"
|
|
202
|
+
version = "0.3.1"
|
|
203
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
204
|
+
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
|
205
|
+
dependencies = [
|
|
206
|
+
"ppv-lite86",
|
|
207
|
+
"rand_core",
|
|
208
|
+
]
|
|
209
|
+
|
|
210
|
+
[[package]]
|
|
211
|
+
name = "rand_core"
|
|
212
|
+
version = "0.6.4"
|
|
213
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
214
|
+
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
|
215
|
+
dependencies = [
|
|
216
|
+
"getrandom",
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
[[package]]
|
|
220
|
+
name = "sha2"
|
|
221
|
+
version = "0.10.9"
|
|
222
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
223
|
+
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
|
224
|
+
dependencies = [
|
|
225
|
+
"cfg-if",
|
|
226
|
+
"cpufeatures",
|
|
227
|
+
"digest",
|
|
228
|
+
]
|
|
229
|
+
|
|
230
|
+
[[package]]
|
|
231
|
+
name = "syn"
|
|
232
|
+
version = "2.0.117"
|
|
233
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
234
|
+
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
|
235
|
+
dependencies = [
|
|
236
|
+
"proc-macro2",
|
|
237
|
+
"quote",
|
|
238
|
+
"unicode-ident",
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
[[package]]
|
|
242
|
+
name = "target-lexicon"
|
|
243
|
+
version = "0.13.5"
|
|
244
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
245
|
+
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
246
|
+
|
|
247
|
+
[[package]]
|
|
248
|
+
name = "typenum"
|
|
249
|
+
version = "1.19.0"
|
|
250
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
251
|
+
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
|
252
|
+
|
|
253
|
+
[[package]]
|
|
254
|
+
name = "unicode-ident"
|
|
255
|
+
version = "1.0.24"
|
|
256
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
257
|
+
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
258
|
+
|
|
259
|
+
[[package]]
|
|
260
|
+
name = "version_check"
|
|
261
|
+
version = "0.9.5"
|
|
262
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
263
|
+
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
|
264
|
+
|
|
265
|
+
[[package]]
|
|
266
|
+
name = "wasi"
|
|
267
|
+
version = "0.11.1+wasi-snapshot-preview1"
|
|
268
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
269
|
+
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
|
270
|
+
|
|
271
|
+
[[package]]
|
|
272
|
+
name = "zerocopy"
|
|
273
|
+
version = "0.8.48"
|
|
274
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
275
|
+
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
|
|
276
|
+
dependencies = [
|
|
277
|
+
"zerocopy-derive",
|
|
278
|
+
]
|
|
279
|
+
|
|
280
|
+
[[package]]
|
|
281
|
+
name = "zerocopy-derive"
|
|
282
|
+
version = "0.8.48"
|
|
283
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
284
|
+
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
|
285
|
+
dependencies = [
|
|
286
|
+
"proc-macro2",
|
|
287
|
+
"quote",
|
|
288
|
+
"syn",
|
|
289
|
+
]
|
|
290
|
+
|
|
291
|
+
[[package]]
|
|
292
|
+
name = "zeroize"
|
|
293
|
+
version = "1.8.2"
|
|
294
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
295
|
+
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
|
296
|
+
dependencies = [
|
|
297
|
+
"zeroize_derive",
|
|
298
|
+
]
|
|
299
|
+
|
|
300
|
+
[[package]]
|
|
301
|
+
name = "zeroize_derive"
|
|
302
|
+
version = "1.4.3"
|
|
303
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
304
|
+
checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
|
|
305
|
+
dependencies = [
|
|
306
|
+
"proc-macro2",
|
|
307
|
+
"quote",
|
|
308
|
+
"syn",
|
|
309
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "pyquorum_core"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2024"
|
|
5
|
+
|
|
6
|
+
[lib]
|
|
7
|
+
name = "pyquorum_core"
|
|
8
|
+
crate-type = ["cdylib"]
|
|
9
|
+
|
|
10
|
+
[dependencies]
|
|
11
|
+
pyo3 = { version = "0.28.3", features = ["extension-module", "abi3-py39"] }
|
|
12
|
+
rand = "0.8"
|
|
13
|
+
zeroize = { version = "1.7", features = ["derive"] }
|
|
14
|
+
sha2 = "0.10"
|
|
15
|
+
|
|
16
|
+
[profile.release]
|
|
17
|
+
opt-level = 3
|
|
18
|
+
strip = true
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
use pyo3::exceptions::PyValueError;
|
|
2
|
+
use pyo3::prelude::*;
|
|
3
|
+
use pyo3::types::PyBytes;
|
|
4
|
+
use rand::rngs::OsRng;
|
|
5
|
+
use rand::RngCore;
|
|
6
|
+
use zeroize::{Zeroize, ZeroizeOnDrop};
|
|
7
|
+
|
|
8
|
+
// ── Константы ─────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
const PRIME: u128 = (1u128 << 127) - 1;
|
|
11
|
+
const KEY_LEN: usize = 32;
|
|
12
|
+
|
|
13
|
+
// ── Защищённый тип ────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
#[derive(Zeroize, ZeroizeOnDrop, Clone)]
|
|
16
|
+
struct SecretKey([u8; KEY_LEN]);
|
|
17
|
+
|
|
18
|
+
// ── Арифметика GF(p) (без изменений) ──────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
fn mul_mod(a: u128, b: u128, m: u128) -> u128 {
|
|
21
|
+
let mut result = 0u128;
|
|
22
|
+
let mut base = a % m;
|
|
23
|
+
let mut exp = b;
|
|
24
|
+
while exp > 0 {
|
|
25
|
+
if exp & 1 == 1 {
|
|
26
|
+
result = (result + base) % m;
|
|
27
|
+
}
|
|
28
|
+
base = (base << 1) % m;
|
|
29
|
+
exp >>= 1;
|
|
30
|
+
}
|
|
31
|
+
result
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
fn mod_pow(mut base: u128, mut exp: u128, modulus: u128) -> u128 {
|
|
35
|
+
let mut result = 1u128;
|
|
36
|
+
base %= modulus;
|
|
37
|
+
while exp > 0 {
|
|
38
|
+
if exp & 1 == 1 {
|
|
39
|
+
result = mul_mod(result, base, modulus);
|
|
40
|
+
}
|
|
41
|
+
exp >>= 1;
|
|
42
|
+
base = mul_mod(base, base, modulus);
|
|
43
|
+
}
|
|
44
|
+
result
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fn mod_inv(a: u128, p: u128) -> u128 {
|
|
48
|
+
mod_pow(a, p - 2, p)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── Shamir's Secret Sharing (внутренняя логика сохранена) ──────────────────
|
|
52
|
+
|
|
53
|
+
fn shamir_split_internal(secret: &[u8; KEY_LEN], k: usize, n: usize) -> Result<Vec<String>, String> {
|
|
54
|
+
if k < 2 || n < k {
|
|
55
|
+
return Err("Invalid k/n parameters".into());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let secrets = [
|
|
59
|
+
u64::from_be_bytes(secret[0..8].try_into().unwrap()) as u128,
|
|
60
|
+
u64::from_be_bytes(secret[8..16].try_into().unwrap()) as u128,
|
|
61
|
+
u64::from_be_bytes(secret[16..24].try_into().unwrap()) as u128,
|
|
62
|
+
u64::from_be_bytes(secret[24..32].try_into().unwrap()) as u128,
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
let mut rng = OsRng;
|
|
66
|
+
let mut rand_coeff = || {
|
|
67
|
+
let lo = rng.next_u64() as u128;
|
|
68
|
+
let hi = (rng.next_u64() as u128) & 0x7FFF_FFFF_FFFF_FFFF;
|
|
69
|
+
(hi << 64 | lo) % PRIME
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
let coeffs: Vec<Vec<u128>> = secrets
|
|
73
|
+
.iter()
|
|
74
|
+
.map(|_| (0..k - 1).map(|_| rand_coeff()).collect())
|
|
75
|
+
.collect();
|
|
76
|
+
|
|
77
|
+
let eval_poly = |s: u128, c_list: &[u128], x: u128| -> u128 {
|
|
78
|
+
let mut result = s;
|
|
79
|
+
let mut x_pow = x;
|
|
80
|
+
for &c in c_list {
|
|
81
|
+
result = (result + mul_mod(c, x_pow, PRIME)) % PRIME;
|
|
82
|
+
x_pow = mul_mod(x_pow, x, PRIME);
|
|
83
|
+
}
|
|
84
|
+
result
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
let shares = (1..=n as u128)
|
|
88
|
+
.map(|x| {
|
|
89
|
+
let res: Vec<String> = secrets
|
|
90
|
+
.iter()
|
|
91
|
+
.zip(&coeffs)
|
|
92
|
+
.map(|(&s, c)| format!("{:032x}", eval_poly(s, c, x)))
|
|
93
|
+
.collect();
|
|
94
|
+
format!("{}:{}", x, res.join(":"))
|
|
95
|
+
})
|
|
96
|
+
.collect();
|
|
97
|
+
|
|
98
|
+
Ok(shares)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
fn shamir_combine_internal(shares: &[String], k: usize) -> Result<SecretKey, String> {
|
|
102
|
+
if shares.len() < k {
|
|
103
|
+
return Err(format!("Need at least {} shares", k));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let mut parsed = Vec::with_capacity(k);
|
|
107
|
+
for s in &shares[..k] {
|
|
108
|
+
let p: Vec<&str> = s.split(':').collect();
|
|
109
|
+
if p.len() != 5 { return Err("Invalid format".into()); }
|
|
110
|
+
let idx = p[0].parse::<u128>().map_err(|e| e.to_string())?;
|
|
111
|
+
let vals = [
|
|
112
|
+
u128::from_str_radix(p[1], 16).map_err(|e| e.to_string())?,
|
|
113
|
+
u128::from_str_radix(p[2], 16).map_err(|e| e.to_string())?,
|
|
114
|
+
u128::from_str_radix(p[3], 16).map_err(|e| e.to_string())?,
|
|
115
|
+
u128::from_str_radix(p[4], 16).map_err(|e| e.to_string())?,
|
|
116
|
+
];
|
|
117
|
+
parsed.push((idx, vals));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let mut key_bytes = [0u8; KEY_LEN];
|
|
121
|
+
for comp in 0..4 {
|
|
122
|
+
let mut result = 0u128;
|
|
123
|
+
for i in 0..k {
|
|
124
|
+
let (xi, yi) = (parsed[i].0, parsed[i].1[comp]);
|
|
125
|
+
let (mut num, mut den) = (1u128, 1u128);
|
|
126
|
+
for j in 0..k {
|
|
127
|
+
if i == j { continue; }
|
|
128
|
+
let xj = parsed[j].0;
|
|
129
|
+
num = mul_mod(num, PRIME - (xj % PRIME), PRIME);
|
|
130
|
+
let diff = if xi > xj { xi - xj } else { PRIME - (xj - xi) % PRIME };
|
|
131
|
+
den = mul_mod(den, diff, PRIME);
|
|
132
|
+
}
|
|
133
|
+
let li = mul_mod(num, mod_inv(den, PRIME), PRIME);
|
|
134
|
+
result = (result + mul_mod(yi, li, PRIME)) % PRIME;
|
|
135
|
+
}
|
|
136
|
+
key_bytes[comp * 8..(comp + 1) * 8].copy_from_slice(&(result as u64).to_be_bytes());
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
Ok(SecretKey(key_bytes))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── PyO3 bindings (Modern Bound API) ──────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
#[pyfunction]
|
|
145
|
+
/// Генерирует случайный 256-битный ключ. Возвращает bytes.
|
|
146
|
+
fn generate_key(py: Python<'_>) -> PyResult<Bound<'_, PyBytes>> {
|
|
147
|
+
let mut key = [0u8; KEY_LEN];
|
|
148
|
+
OsRng.fill_bytes(&mut key);
|
|
149
|
+
|
|
150
|
+
// В 0.28.3 используем просто .new(), он возвращает Bound
|
|
151
|
+
let result = PyBytes::new(py, &key);
|
|
152
|
+
|
|
153
|
+
key.zeroize();
|
|
154
|
+
Ok(result)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
#[pyfunction]
|
|
158
|
+
/// Разбивает секрет на n частей.
|
|
159
|
+
fn split_secret(secret: &[u8], k: usize, n: usize) -> PyResult<Vec<String>> {
|
|
160
|
+
let key: [u8; KEY_LEN] = secret
|
|
161
|
+
.try_into()
|
|
162
|
+
.map_err(|_| PyValueError::new_err(format!("Secret must be {} bytes", KEY_LEN)))?;
|
|
163
|
+
|
|
164
|
+
shamir_split_internal(&key, k, n)
|
|
165
|
+
.map_err(|e| PyValueError::new_err(e))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#[pyfunction]
|
|
169
|
+
/// Восстанавливает секрет. Возвращает bytes.
|
|
170
|
+
fn combine_shares<'py>(py: Python<'py>, shares: Vec<String>, k: usize) -> PyResult<Bound<'py, PyBytes>> {
|
|
171
|
+
let key = shamir_combine_internal(&shares, k)
|
|
172
|
+
.map_err(|e| PyValueError::new_err(e))?;
|
|
173
|
+
|
|
174
|
+
// Снова используем .new() вместо .new_bound()
|
|
175
|
+
Ok(PyBytes::new(py, &key.0))
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
#[pymodule]
|
|
179
|
+
fn pyquorum_core(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
180
|
+
m.add_function(wrap_pyfunction!(generate_key, m)?)?;
|
|
181
|
+
m.add_function(wrap_pyfunction!(split_secret, m)?)?;
|
|
182
|
+
m.add_function(wrap_pyfunction!(combine_shares, m)?)?;
|
|
183
|
+
Ok(())
|
|
184
|
+
}
|