chik-base 0.1.7__py3-none-any.whl
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.
- chik_base/__init__.py +0 -0
- chik_base/atoms/__init__.py +27 -0
- chik_base/atoms/hexbytes.py +11 -0
- chik_base/atoms/ints.py +59 -0
- chik_base/atoms/sized_bytes.py +43 -0
- chik_base/atoms/struct_stream.py +24 -0
- chik_base/bls12_381/__init__.py +11 -0
- chik_base/bls12_381/bls_public_key.py +129 -0
- chik_base/bls12_381/bls_secret_exponent.py +138 -0
- chik_base/bls12_381/bls_signature.py +81 -0
- chik_base/bls12_381/secret_key_utils.py +24 -0
- chik_base/cbincode/__init__.py +43 -0
- chik_base/cbincode/parser.py +163 -0
- chik_base/cbincode/streamer.py +190 -0
- chik_base/cbincode/util.py +29 -0
- chik_base/contrib/__init__.py +0 -0
- chik_base/contrib/bech32m.py +150 -0
- chik_base/core/__init__.py +6 -0
- chik_base/core/coin.py +24 -0
- chik_base/core/coin_spend.py +17 -0
- chik_base/core/conlang.py +41 -0
- chik_base/core/spend_bundle.py +25 -0
- chik_base/meta/__init__.py +0 -0
- chik_base/meta/optional.py +16 -0
- chik_base/meta/type_tree.py +70 -0
- chik_base/meta/typing.py +17 -0
- chik_base/py.typed +0 -0
- chik_base/util/__init__.py +0 -0
- chik_base/util/bech32.py +30 -0
- chik_base/util/std_hash.py +13 -0
- chik_base-0.1.7.dist-info/LICENSE +201 -0
- chik_base-0.1.7.dist-info/METADATA +17 -0
- chik_base-0.1.7.dist-info/RECORD +35 -0
- chik_base-0.1.7.dist-info/WHEEL +5 -0
- chik_base-0.1.7.dist-info/top_level.txt +1 -0
chik_base/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines some useful atomic types for building more complex
|
|
3
|
+
data structures.
|
|
4
|
+
|
|
5
|
+
Except for `hexbytes` (which is just a hex-printing subclass of `bytes`,
|
|
6
|
+
these types are all fixed-sized, simplifying serializing and parsing, like
|
|
7
|
+
those used in `cbincode` (the slight modification to the `bincode` standard that
|
|
8
|
+
chik uses).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .hexbytes import hexbytes
|
|
12
|
+
from .ints import int8, uint8, int16, uint16, int32, uint32, int64, uint64
|
|
13
|
+
from .sized_bytes import bytes32
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"int8",
|
|
18
|
+
"int16",
|
|
19
|
+
"int32",
|
|
20
|
+
"int64",
|
|
21
|
+
"uint8",
|
|
22
|
+
"uint16",
|
|
23
|
+
"uint32",
|
|
24
|
+
"uint64",
|
|
25
|
+
"hexbytes",
|
|
26
|
+
"bytes32",
|
|
27
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class hexbytes(bytes):
|
|
2
|
+
"""
|
|
3
|
+
This is a subclass of bytes that prints itself out as hex,
|
|
4
|
+
which is much easier on the eyes for binary data that is very non-ascii .
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
def __str__(self):
|
|
8
|
+
return self.hex()
|
|
9
|
+
|
|
10
|
+
def __repr__(self):
|
|
11
|
+
return "<%s: %s>" % (self.__class__.__name__, str(self))
|
chik_base/atoms/ints.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Some ints of fixed size. Their fixed size makes them easier to parse and serialize.
|
|
3
|
+
|
|
4
|
+
Each implements these two class methods:
|
|
5
|
+
|
|
6
|
+
* `.parse(f: BinaryIO)`
|
|
7
|
+
* `._class_stream(obj, f: BinaryIO)`
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .struct_stream import struct_stream
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class int8(int, struct_stream):
|
|
15
|
+
"signed 8-bit int"
|
|
16
|
+
|
|
17
|
+
PACK = "!b"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class uint8(int, struct_stream):
|
|
21
|
+
"unsigned 8-bit int"
|
|
22
|
+
|
|
23
|
+
PACK = "!B"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class int16(int, struct_stream):
|
|
27
|
+
"signed 16-bit int"
|
|
28
|
+
|
|
29
|
+
PACK = "!h"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class uint16(int, struct_stream):
|
|
33
|
+
"unsigned 16-bit int"
|
|
34
|
+
|
|
35
|
+
PACK = "!H"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class int32(int, struct_stream):
|
|
39
|
+
"signed 32-bit int"
|
|
40
|
+
|
|
41
|
+
PACK = "!l"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class uint32(int, struct_stream):
|
|
45
|
+
"unsigned 32-bit int"
|
|
46
|
+
|
|
47
|
+
PACK = "!L"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class int64(int, struct_stream):
|
|
51
|
+
"signed 64-bit int"
|
|
52
|
+
|
|
53
|
+
PACK = "!q"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class uint64(int, struct_stream):
|
|
57
|
+
"unsigned 64-bit int"
|
|
58
|
+
|
|
59
|
+
PACK = "!Q"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from typing import BinaryIO
|
|
2
|
+
from .hexbytes import hexbytes
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SizedBytes(hexbytes):
|
|
6
|
+
"""
|
|
7
|
+
Intended to be subclassed, this base class makes it easy to create
|
|
8
|
+
subclasses of `bytes` that require the length to be a specific value.
|
|
9
|
+
|
|
10
|
+
Having a specific number of bytes means we can easily parse and stream
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
_size: int
|
|
14
|
+
|
|
15
|
+
def __new__(cls, v):
|
|
16
|
+
"`v` must be castable to `bytes`"
|
|
17
|
+
v = bytes(v)
|
|
18
|
+
if not isinstance(v, bytes) or len(v) != cls._size:
|
|
19
|
+
raise ValueError("bad %s initializer %s" % (cls.__name__, v))
|
|
20
|
+
return hexbytes.__new__(cls, v)
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def parse(cls, f: BinaryIO) -> bytes:
|
|
24
|
+
b = f.read(cls._size)
|
|
25
|
+
if len(b) != cls._size:
|
|
26
|
+
msg = f"unexpected EOS: {len(b)} bytes read, {cls._size} expected"
|
|
27
|
+
raise ValueError(msg)
|
|
28
|
+
return cls(b)
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def _class_stream(cls, obj: bytes, f: BinaryIO) -> None:
|
|
32
|
+
if len(obj) != cls._size:
|
|
33
|
+
msg = f"got {len(obj)} bytes when we expected {cls._size}"
|
|
34
|
+
raise ValueError(msg)
|
|
35
|
+
f.write(obj)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class bytes32(SizedBytes):
|
|
39
|
+
"""
|
|
40
|
+
A subclass of `bytes` that requires the length to be 32.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
_size = 32
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
|
|
3
|
+
from typing import BinaryIO, TypeVar, Type
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
_T = TypeVar("_T", bound="struct_stream")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class struct_stream:
|
|
10
|
+
"""
|
|
11
|
+
This is a base class. Subclasses should define `cls.PACK` as a struct.pack
|
|
12
|
+
template string. In return, you get implementations of `parse` and
|
|
13
|
+
`_class_stream`.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
PACK: str
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def parse(cls: Type[_T], f: BinaryIO) -> _T:
|
|
20
|
+
return cls(*struct.unpack(cls.PACK, f.read(struct.calcsize(cls.PACK))))
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def _class_stream(cls: Type[_T], obj: _T, f: BinaryIO) -> None:
|
|
24
|
+
f.write(struct.pack(cls.PACK, obj))
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The `chik_rs` wheel has some rough edges in its api. This module smooths them over
|
|
3
|
+
with a more ergonomic api.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .bls_public_key import BLSPublicKey
|
|
7
|
+
from .bls_secret_exponent import BLSSecretExponent
|
|
8
|
+
from .bls_signature import BLSSignature
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = ["BLSPublicKey", "BLSSecretExponent", "BLSSignature"]
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from typing import BinaryIO, List
|
|
2
|
+
|
|
3
|
+
import chik_rs # type: ignore
|
|
4
|
+
|
|
5
|
+
from chik_base.atoms import hexbytes
|
|
6
|
+
from chik_base.util.bech32 import bech32_decode, bech32_encode, Encoding
|
|
7
|
+
|
|
8
|
+
from .secret_key_utils import public_key_from_int
|
|
9
|
+
|
|
10
|
+
BECH32M_PUBLIC_KEY_PREFIX = "bls1238"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BLSPublicKey:
|
|
14
|
+
"""
|
|
15
|
+
This corresponds to an element in bls12-381's G1 group, represented
|
|
16
|
+
when serialized by a 48-byte x element (with a few extra bits at the
|
|
17
|
+
beginning for metadata).
|
|
18
|
+
"""
|
|
19
|
+
def __init__(self, g1: chik_rs.G1Element):
|
|
20
|
+
assert isinstance(g1, chik_rs.G1Element)
|
|
21
|
+
self._g1 = g1
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def from_bytes(cls, blob):
|
|
25
|
+
"parse from a binary blob"
|
|
26
|
+
bls_public_hd_key = chik_rs.G1Element.from_bytes(blob)
|
|
27
|
+
return BLSPublicKey(bls_public_hd_key)
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def parse(cls, f: BinaryIO):
|
|
31
|
+
"parse from a stream"
|
|
32
|
+
return cls.from_bytes(f.read(48))
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def generator(cls):
|
|
36
|
+
"return the well-known generator"
|
|
37
|
+
return BLSPublicKey(chik_rs.G1Element.generator())
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def zero(cls):
|
|
41
|
+
"return the well-known zero"
|
|
42
|
+
return cls(chik_rs.G1Element())
|
|
43
|
+
|
|
44
|
+
def stream(self, f: BinaryIO) -> None:
|
|
45
|
+
"write the serialized version to the file f"
|
|
46
|
+
f.write(bytes(self._g1))
|
|
47
|
+
|
|
48
|
+
def __add__(self, other):
|
|
49
|
+
"add two elements, returning the sum. Use `+`"
|
|
50
|
+
return BLSPublicKey(self._g1 + other._g1)
|
|
51
|
+
|
|
52
|
+
def __mul__(self, other: int):
|
|
53
|
+
"multiply an element by a scalar"
|
|
54
|
+
if other < 0:
|
|
55
|
+
raise ValueError("can't multiply by a negative value")
|
|
56
|
+
if self == self.generator():
|
|
57
|
+
# there is a special method in chik_rs that multiplies
|
|
58
|
+
# the generator by an integer that is not susceptible to
|
|
59
|
+
# timing attacks. Since public keys are generate times integer,
|
|
60
|
+
# using this method with the generator could expose to timing
|
|
61
|
+
# attacks. So instead we use the more clever code specifically
|
|
62
|
+
# for the generator
|
|
63
|
+
return BLSPublicKey(public_key_from_int(other))
|
|
64
|
+
if other == 0:
|
|
65
|
+
return self.zero()
|
|
66
|
+
if other == 1:
|
|
67
|
+
return self
|
|
68
|
+
# use recursion on `__mul__` in a sneaky clever way
|
|
69
|
+
parity = other & 1
|
|
70
|
+
v = self.__mul__(other >> 1)
|
|
71
|
+
v += v
|
|
72
|
+
if parity:
|
|
73
|
+
v += self
|
|
74
|
+
return v
|
|
75
|
+
|
|
76
|
+
def __rmul__(self, other: int):
|
|
77
|
+
return self.__mul__(other)
|
|
78
|
+
|
|
79
|
+
def __eq__(self, other):
|
|
80
|
+
if isinstance(other, type(self)):
|
|
81
|
+
return self._g1 == other._g1
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
def __bytes__(self) -> bytes:
|
|
85
|
+
return hexbytes(self._g1)
|
|
86
|
+
|
|
87
|
+
def child(self, index: int) -> "BLSPublicKey":
|
|
88
|
+
"unhardened child derivation"
|
|
89
|
+
return BLSPublicKey(
|
|
90
|
+
chik_rs.AugSchemeMPL.derive_child_pk_unhardened(self._g1, index)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def child_for_path(self, path: List[int]) -> "BLSPublicKey":
|
|
94
|
+
"A path is a list of child integer derivations"
|
|
95
|
+
r = self
|
|
96
|
+
for index in path:
|
|
97
|
+
r = r.child(index)
|
|
98
|
+
return r
|
|
99
|
+
|
|
100
|
+
def fingerprint(self) -> int:
|
|
101
|
+
"return a 32-bit unsigned integer"
|
|
102
|
+
return self._g1.get_fingerprint()
|
|
103
|
+
|
|
104
|
+
def as_bech32m(self) -> str:
|
|
105
|
+
"convert to a bech32m string"
|
|
106
|
+
return bech32_encode(BECH32M_PUBLIC_KEY_PREFIX, bytes(self), Encoding.BECH32M)
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def from_bech32m(cls, text: str) -> "BLSPublicKey":
|
|
110
|
+
"convert from a bech32m string"
|
|
111
|
+
r = bech32_decode(text, max_length=91)
|
|
112
|
+
if r is not None:
|
|
113
|
+
prefix, base8_data, encoding = r
|
|
114
|
+
if (
|
|
115
|
+
encoding == Encoding.BECH32M
|
|
116
|
+
and prefix == BECH32M_PUBLIC_KEY_PREFIX
|
|
117
|
+
and len(base8_data) == 49
|
|
118
|
+
):
|
|
119
|
+
return cls.from_bytes(base8_data[:48])
|
|
120
|
+
raise ValueError("not bls12_381 bech32m pubkey")
|
|
121
|
+
|
|
122
|
+
def __hash__(self):
|
|
123
|
+
return bytes(self).__hash__()
|
|
124
|
+
|
|
125
|
+
def __str__(self):
|
|
126
|
+
return self.as_bech32m()
|
|
127
|
+
|
|
128
|
+
def __repr__(self):
|
|
129
|
+
return "<%s: %s>" % (self.__class__.__name__, self)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from typing import BinaryIO, List, Optional
|
|
2
|
+
|
|
3
|
+
import chik_rs # type: ignore
|
|
4
|
+
|
|
5
|
+
from chik_base.util.bech32 import bech32_decode, bech32_encode, Encoding
|
|
6
|
+
|
|
7
|
+
from .bls_public_key import BLSPublicKey
|
|
8
|
+
from .bls_signature import BLSSignature
|
|
9
|
+
from .secret_key_utils import private_key_from_int
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
BECH32M_SECRET_EXPONENT_PREFIX = "se"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BLSSecretExponent:
|
|
16
|
+
"""
|
|
17
|
+
This is essentially an `int` with convenience functions.
|
|
18
|
+
|
|
19
|
+
We don't subclass `int` because we have a different implementation of
|
|
20
|
+
`__bytes__` which could cause confusion.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, sk: chik_rs.PrivateKey):
|
|
24
|
+
self._sk = sk
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def from_seed(cls, seed: bytes) -> "BLSSecretExponent":
|
|
28
|
+
"""
|
|
29
|
+
convert from a seed using the specification at
|
|
30
|
+
https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05
|
|
31
|
+
"""
|
|
32
|
+
return BLSSecretExponent(chik_rs.AugSchemeMPL.key_gen(seed))
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def from_int(cls, secret_exponent) -> "BLSSecretExponent":
|
|
36
|
+
"convert from the given int"
|
|
37
|
+
return cls(private_key_from_int(secret_exponent))
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def from_bytes(cls, blob) -> "BLSSecretExponent":
|
|
41
|
+
"deserialize from the given blob. A blob of length 32 bytes is expected"
|
|
42
|
+
return cls(chik_rs.PrivateKey.from_bytes(blob))
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def parse(cls, f: BinaryIO):
|
|
46
|
+
"deserialize from the given stream"
|
|
47
|
+
return cls.from_bytes(f.read(32))
|
|
48
|
+
|
|
49
|
+
def stream(self, f: BinaryIO) -> None:
|
|
50
|
+
"serialize to the given stream"
|
|
51
|
+
f.write(bytes(self._sk))
|
|
52
|
+
|
|
53
|
+
def fingerprint(self) -> int:
|
|
54
|
+
"return a 32-bit unsigned integer. The public key fingerprint will match."
|
|
55
|
+
return self._sk.get_g1().get_fingerprint()
|
|
56
|
+
|
|
57
|
+
def sign(
|
|
58
|
+
self, message: bytes, final_public_key: Optional[BLSPublicKey] = None
|
|
59
|
+
) -> BLSSignature:
|
|
60
|
+
"generate a signature"
|
|
61
|
+
if final_public_key:
|
|
62
|
+
return BLSSignature(
|
|
63
|
+
chik_rs.AugSchemeMPL.sign(self._sk, message, final_public_key._g1)
|
|
64
|
+
)
|
|
65
|
+
return BLSSignature(chik_rs.AugSchemeMPL.sign(self._sk, message))
|
|
66
|
+
|
|
67
|
+
def public_key(self) -> BLSPublicKey:
|
|
68
|
+
"return the corresponding public key"
|
|
69
|
+
return BLSPublicKey(self._sk.get_g1())
|
|
70
|
+
|
|
71
|
+
def secret_exponent(self) -> int:
|
|
72
|
+
"return the exponent as an `int`"
|
|
73
|
+
return int.from_bytes(bytes(self), "big")
|
|
74
|
+
|
|
75
|
+
def hardened_child(self, index: int) -> "BLSSecretExponent":
|
|
76
|
+
"return the hardened child"
|
|
77
|
+
return BLSSecretExponent(chik_rs.AugSchemeMPL.derive_child_sk(self._sk, index))
|
|
78
|
+
|
|
79
|
+
def child(self, index: int) -> "BLSSecretExponent":
|
|
80
|
+
"return the unhardened child. This will match the corresponding public_key child"
|
|
81
|
+
return BLSSecretExponent(
|
|
82
|
+
chik_rs.AugSchemeMPL.derive_child_sk_unhardened(self._sk, index)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def child_for_path(self, path: List[int]) -> "BLSSecretExponent":
|
|
86
|
+
"A path is a list of child integer derivations. Unhardened only"
|
|
87
|
+
r = self
|
|
88
|
+
for index in path:
|
|
89
|
+
r = r.child(index)
|
|
90
|
+
return r
|
|
91
|
+
|
|
92
|
+
def as_bech32m(self):
|
|
93
|
+
"convert to a bech32m string"
|
|
94
|
+
return bech32_encode(
|
|
95
|
+
BECH32M_SECRET_EXPONENT_PREFIX, bytes(self), Encoding.BECH32M
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def from_bech32m(cls, text: str) -> "BLSSecretExponent":
|
|
100
|
+
"convert from a bech32m string"
|
|
101
|
+
r = bech32_decode(text)
|
|
102
|
+
if r is not None:
|
|
103
|
+
prefix, base8_data, encoding = r
|
|
104
|
+
if (
|
|
105
|
+
encoding == Encoding.BECH32M
|
|
106
|
+
and prefix == BECH32M_SECRET_EXPONENT_PREFIX
|
|
107
|
+
and len(base8_data) == 33
|
|
108
|
+
):
|
|
109
|
+
return cls.from_bytes(base8_data[:32])
|
|
110
|
+
raise ValueError("not secret exponent")
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def zero(cls) -> "BLSSecretExponent":
|
|
114
|
+
"returns the secret exponent corresponding to 0. This shouldn't be used to sign"
|
|
115
|
+
return ZERO
|
|
116
|
+
|
|
117
|
+
def __add__(self, other):
|
|
118
|
+
return self.from_int(int(self) + int(other))
|
|
119
|
+
|
|
120
|
+
def __int__(self):
|
|
121
|
+
return self.secret_exponent()
|
|
122
|
+
|
|
123
|
+
def __eq__(self, other):
|
|
124
|
+
if isinstance(other, int):
|
|
125
|
+
other = BLSSecretExponent.from_int(other)
|
|
126
|
+
return self._sk == other._sk
|
|
127
|
+
|
|
128
|
+
def __bytes__(self):
|
|
129
|
+
return bytes(self._sk)
|
|
130
|
+
|
|
131
|
+
def __str__(self):
|
|
132
|
+
return "<prv for:%s>" % self.public_key()
|
|
133
|
+
|
|
134
|
+
def __repr__(self):
|
|
135
|
+
return "<%s: %s>" % (self.__class__.__name__, self)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
ZERO = BLSSecretExponent.from_int(0)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import BinaryIO, List, Sequence, Tuple
|
|
3
|
+
|
|
4
|
+
import chik_rs # type: ignore
|
|
5
|
+
|
|
6
|
+
from chik_base.atoms import bytes32
|
|
7
|
+
|
|
8
|
+
from .bls_public_key import BLSPublicKey
|
|
9
|
+
|
|
10
|
+
ZERO96 = bytes([0] * 96)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BLSSignature:
|
|
14
|
+
"""
|
|
15
|
+
This wraps the chik_rs version and resolves a couple edge cases
|
|
16
|
+
around aggregation and validation.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class aggsig_pair:
|
|
21
|
+
public_key: BLSPublicKey
|
|
22
|
+
message_hash: bytes
|
|
23
|
+
|
|
24
|
+
def __init__(self, g2: chik_rs.G2Element):
|
|
25
|
+
assert isinstance(g2, chik_rs.G2Element)
|
|
26
|
+
self._g2 = g2
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def from_bytes(cls, blob):
|
|
30
|
+
"parse from a binary blob"
|
|
31
|
+
bls_public_hd_key = chik_rs.G2Element.from_bytes(blob)
|
|
32
|
+
return cls(bls_public_hd_key)
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def parse(cls, f: BinaryIO):
|
|
36
|
+
"parse from a stream"
|
|
37
|
+
return cls.from_bytes(f.read(96))
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def generator(cls):
|
|
41
|
+
"return the well-known generator"
|
|
42
|
+
return cls(chik_rs.G2Element.generator())
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def zero(cls):
|
|
46
|
+
"returns the g2 element corresponding to 0. This shouldn't be used to sign"
|
|
47
|
+
return cls(chik_rs.G2Element())
|
|
48
|
+
|
|
49
|
+
def stream(self, f):
|
|
50
|
+
"write the serialized version to the file f"
|
|
51
|
+
f.write(bytes(self._g2))
|
|
52
|
+
|
|
53
|
+
def __add__(self, other):
|
|
54
|
+
"add two elements, returning the sum. Use `+`"
|
|
55
|
+
return self.__class__(self._g2 + other._g2)
|
|
56
|
+
|
|
57
|
+
def __eq__(self, other):
|
|
58
|
+
return self._g2 == other._g2
|
|
59
|
+
|
|
60
|
+
def __bytes__(self) -> bytes:
|
|
61
|
+
return bytes(self._g2)
|
|
62
|
+
|
|
63
|
+
def __str__(self):
|
|
64
|
+
return bytes(self._g2).hex()
|
|
65
|
+
|
|
66
|
+
def __repr__(self):
|
|
67
|
+
return "<%s: %s>" % (self.__class__.__name__, self)
|
|
68
|
+
|
|
69
|
+
def validate(self, hash_key_pairs: Sequence[aggsig_pair]) -> bool:
|
|
70
|
+
"check signature"
|
|
71
|
+
return self.verify([(_.public_key, _.message_hash) for _ in hash_key_pairs])
|
|
72
|
+
|
|
73
|
+
def verify(self, hash_key_pairs: Sequence[Tuple[BLSPublicKey, bytes]]) -> bool:
|
|
74
|
+
"check signature"
|
|
75
|
+
hkp = list(hash_key_pairs)
|
|
76
|
+
public_keys: List[chik_rs.G1Element] = [_[0]._g1 for _ in hkp]
|
|
77
|
+
message_hashes: List[bytes32] = [_[1] for _ in hkp]
|
|
78
|
+
|
|
79
|
+
return chik_rs.AugSchemeMPL.aggregate_verify(
|
|
80
|
+
public_keys, message_hashes, self._g2
|
|
81
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Some secret key utilities that need to know the group order and return `chik_rs`
|
|
3
|
+
structures.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
import chik_rs # type: ignore
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
GROUP_ORDER = (
|
|
11
|
+
52435875175126190479447740508185965837690552500527637822603658699938581184513
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def private_key_from_int(secret_exponent: int) -> chik_rs.PrivateKey:
|
|
16
|
+
"convert an `int` into the `chik_rs.PrivateKey`"
|
|
17
|
+
secret_exponent %= GROUP_ORDER
|
|
18
|
+
blob = secret_exponent.to_bytes(32, "big")
|
|
19
|
+
return chik_rs.PrivateKey.from_bytes(blob)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def public_key_from_int(secret_exponent: int) -> chik_rs.G1Element:
|
|
23
|
+
"convert an `int` into the corresponding `chik_rs.G1Element` multiple of generator"
|
|
24
|
+
return private_key_from_int(secret_exponent).get_g1()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Implement `cbincode` streaming and parsing.
|
|
3
|
+
|
|
4
|
+
This is a very simple serialization standard. It's almost identical to the
|
|
5
|
+
bincode standard implemented by a rust crate and described here:
|
|
6
|
+
|
|
7
|
+
https://github.com/bincode-org/bincode/blob/trunk/docs/spec.md
|
|
8
|
+
|
|
9
|
+
It uses `FixintEncoding`, ie. a fixed size for each of `(uint/int)(8|16|32)`.
|
|
10
|
+
The bincode standard uses uint32 for size prefixes (on lists, for example) and
|
|
11
|
+
the cbincode standard uses uint16. Other than that, the encoding has very few
|
|
12
|
+
surprises.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
Parsers and streamers are created at runtime based on a type.
|
|
16
|
+
|
|
17
|
+
Supported types include:
|
|
18
|
+
|
|
19
|
+
- `bytes`, `str`
|
|
20
|
+
- any class with a `.parse` class function
|
|
21
|
+
- this includes, `(u)?int(8|16|32)`, `bytes32`
|
|
22
|
+
- `list[T]` where `T` is supported
|
|
23
|
+
- `tuple[T1, T2, ..., TN]` where each `Tn` is supported
|
|
24
|
+
- `Optional[T]` where `T` is supported (also spelled `T | None`)
|
|
25
|
+
- classes decorated with `@dataclass` where each field is of a supported type
|
|
26
|
+
|
|
27
|
+
Transitive closures of the above list are also supported.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from .parser import make_parser, ParseFunction
|
|
31
|
+
from .streamer import make_streamer, StreamFunction
|
|
32
|
+
from .util import from_bytes, from_hex, to_bytes, to_hex
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"make_parser",
|
|
36
|
+
"make_streamer",
|
|
37
|
+
"ParseFunction",
|
|
38
|
+
"StreamFunction",
|
|
39
|
+
"from_hex",
|
|
40
|
+
"from_bytes",
|
|
41
|
+
"to_hex",
|
|
42
|
+
"to_bytes",
|
|
43
|
+
]
|