Skip to content

Commit bbe262c

Browse files
Highjhackerfaustbrian
authored andcommitted
feat: AIP 11 & 18 implementation (#100)
1 parent a6cc259 commit bbe262c

80 files changed

Lines changed: 1610 additions & 449 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ dist/
1212
*.egg-info
1313
.vscode
1414
.vs
15+
venv
16+
venv/

crypto/constants.py

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
from enum import Enum
2+
13
TRANSACTION_TRANSFER = 0
24
TRANSACTION_SECOND_SIGNATURE_REGISTRATION = 1
35
TRANSACTION_DELEGATE_REGISTRATION = 2
46
TRANSACTION_VOTE = 3
57
TRANSACTION_MULTI_SIGNATURE_REGISTRATION = 4
68
TRANSACTION_IPFS = 5
7-
TRANSACTION_TIMELOCK_TRANSFER = 6
8-
TRANSACTION_MULTI_PAYMENT = 7
9-
TRANSACTION_DELEGATE_RESIGNATION = 8
9+
TRANSACTION_MULTI_PAYMENT = 6
10+
TRANSACTION_DELEGATE_RESIGNATION = 7
11+
TRANSACTION_HTLC_LOCK = 8
12+
TRANSACTION_HTLC_CLAIM = 9
13+
TRANSACTION_HTLC_REFUND = 10
1014

1115
TRANSACTION_TYPES = {
1216
TRANSACTION_TRANSFER: 'transfer',
@@ -15,9 +19,11 @@
1519
TRANSACTION_VOTE: 'vote',
1620
TRANSACTION_MULTI_SIGNATURE_REGISTRATION: 'multi_signature_registration',
1721
TRANSACTION_IPFS: 'ipfs',
18-
TRANSACTION_TIMELOCK_TRANSFER: 'timelock_transfer',
1922
TRANSACTION_MULTI_PAYMENT: 'multi_payment',
2023
TRANSACTION_DELEGATE_RESIGNATION: 'delegate_resignation',
24+
TRANSACTION_HTLC_LOCK: 'htlc_lock',
25+
TRANSACTION_HTLC_CLAIM: 'htlc_claim',
26+
TRANSACTION_HTLC_REFUND: 'htlc_refund',
2127
}
2228

2329
TRANSACTION_FEES = {
@@ -26,8 +32,27 @@
2632
TRANSACTION_DELEGATE_REGISTRATION: 2500000000,
2733
TRANSACTION_VOTE: 100000000,
2834
TRANSACTION_MULTI_SIGNATURE_REGISTRATION: 500000000,
29-
TRANSACTION_IPFS: 0,
30-
TRANSACTION_TIMELOCK_TRANSFER: 0,
31-
TRANSACTION_MULTI_PAYMENT: 0,
32-
TRANSACTION_DELEGATE_RESIGNATION: 0,
35+
TRANSACTION_IPFS: 500000000,
36+
TRANSACTION_MULTI_PAYMENT: 10000000,
37+
TRANSACTION_DELEGATE_RESIGNATION: 2500000000,
38+
TRANSACTION_HTLC_LOCK: 10000000,
39+
TRANSACTION_HTLC_CLAIM: 0,
40+
TRANSACTION_HTLC_REFUND: 0,
3341
}
42+
43+
44+
class TRANSACTION_TYPE_GROUP(Enum):
45+
def __int__(self):
46+
return int(self.value)
47+
48+
TEST = 0
49+
CORE = 1
50+
RESERVED = 1000 # Everything above is available to anyone
51+
52+
53+
class HTLC_LOCK_EXPIRATION_TYPE(Enum):
54+
def __str__(self):
55+
return int(self.value)
56+
57+
EPOCH_TIMESTAMP = 1
58+
BLOCK_HEIGHT = 2

crypto/schnorr/__init__.py

Whitespace-only changes.

crypto/schnorr/schnorr.py

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
# From Toons implementation
2+
# see here https://github.com/Moustikitos/dpos/blob/master/dposlib/ark/secp256k1/schnorr.py
3+
4+
import hashlib
5+
from binascii import unhexlify
6+
from builtins import int
7+
8+
p = int(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F)
9+
n = int(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141)
10+
11+
12+
def point_from_encoded(pubkey):
13+
"""
14+
Decode and decompress a ``secp256k1`` point.
15+
Args:
16+
pubkey (:class:`bytes`): compressed and encoded point
17+
Returns:
18+
:class:`list`: ``secp256k1`` point
19+
"""
20+
pubkey = bytearray(pubkey)
21+
x = int_from_bytes(pubkey[1:])
22+
y = y_from_x(x)
23+
if y is None:
24+
raise ValueError('Point not on ``secp256k1`` curve')
25+
elif y % 2 != pubkey[0] - 2:
26+
y = -y % p
27+
return [x, y]
28+
29+
30+
def y_from_x(x):
31+
"""
32+
Compute :class:`P.y` from :class:`P.x` according to ``y²=x³+7``.
33+
"""
34+
y_sq = (pow(x, 3, p) + 7) % p
35+
y = pow(y_sq, (p + 1) // 4, p)
36+
if pow(y, 2, p) != y_sq:
37+
return None
38+
return y
39+
40+
41+
class Point(list):
42+
"""
43+
``secp256k1`` point . Initialization can be done with sole ``x`` value.
44+
:class:`Point` overrides ``*`` and ``+`` operators which accepts
45+
:class:`list` as argument and returns :class:`Point`. It extends
46+
:class:`list` and allows item access using ``__getattr__``/``__setattr__``
47+
operators.
48+
"""
49+
50+
x = property(
51+
lambda cls: list.__getitem__(cls, 0),
52+
lambda cls, v: [
53+
list.__setitem__(cls, 0, int(v)),
54+
list.__setitem__(cls, 1, y_from_x(int(v)))
55+
],
56+
None, ''
57+
)
58+
y = property(
59+
lambda cls: list.__getitem__(cls, 1),
60+
None, None, ''
61+
)
62+
63+
def __init__(self, *xy):
64+
if len(xy) == 0:
65+
xy = (0, None)
66+
elif len(xy) == 1:
67+
xy += (y_from_x(int(xy[0])), )
68+
list.__init__(self, [int(e) if e is not None else e for e in xy[:2]])
69+
70+
def __mul__(self, k):
71+
if isinstance(k, int):
72+
return Point(*point_mul(self, k))
73+
else:
74+
raise TypeError("'%s' should be an int" % k)
75+
__rmul__ = __mul__
76+
77+
def __add__(self, P):
78+
if isinstance(P, list):
79+
return Point(*point_add(self, P))
80+
else:
81+
raise TypeError("'%s' should be a 2-int-length list" % P)
82+
__radd__ = __add__
83+
84+
@staticmethod
85+
def decode(pubkey):
86+
"""
87+
Decode and decompress a ``secp256k1`` point.
88+
"""
89+
return Point(*point_from_encoded(pubkey))
90+
91+
def encode(self):
92+
"""
93+
Decode and decompress a ``secp256k1`` point.
94+
"""
95+
return encoded_from_point(self)
96+
97+
98+
G = Point(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798)
99+
100+
101+
def point_add(P1, P2):
102+
"""
103+
Add ``secp256k1`` points.
104+
Args:
105+
P1 (:class:`list`): first ``secp256k1`` point
106+
P2 (:class:`list`): second ``secp256k1`` point
107+
Returns:
108+
:class:`list`: ``secp256k1`` point
109+
"""
110+
if (P1 is None):
111+
return P2
112+
if (P2 is None):
113+
return P1
114+
if (x(P1) == x(P2) and y(P1) != y(P2)):
115+
raise ValueError('One of the point is not on the curve')
116+
if (P1 == P2):
117+
lam = (3 * x(P1) * x(P1) * pow(2 * y(P1), p - 2, p)) % p
118+
else:
119+
lam = ((y(P2) - y(P1)) * pow(x(P2) - x(P1), p - 2, p)) % p
120+
x3 = (lam * lam - x(P1) - x(P2)) % p
121+
return [x3, (lam * (x(P1) - x3) - y(P1)) % p]
122+
123+
124+
def bcrypto410_verify(msg, pubkey, sig):
125+
if len(msg) != 32:
126+
raise ValueError('The message must be a 32-byte array.')
127+
if len(sig) != 64:
128+
raise ValueError('The signature must be a 64-byte array.')
129+
130+
P = Point.decode(pubkey)
131+
r, s = int_from_bytes(sig[:32]), int_from_bytes(sig[32:])
132+
if r >= p or s >= n:
133+
return False
134+
135+
e = int_from_bytes(hash_sha256(sig[0:32] + pubkey + msg)) % n
136+
R = Point(*(G*s + point_mul(P, n-e))) # P*(n-e) does not work...
137+
if R is None or not is_quad(R.y) or R.x != r:
138+
return False
139+
return True
140+
141+
142+
def b410_schnorr_verify(message, publicKey, signature):
143+
return bcrypto410_verify(
144+
hash_sha256(message),
145+
Point.decode(unhexlify(publicKey)).encode(),
146+
unhexlify(signature)
147+
)
148+
149+
150+
def x(P):
151+
"""
152+
Return :class:`P.x` or :class:`P[0]`.
153+
Args:
154+
P (:class:`list`): ``secp256k1`` point
155+
Returns:
156+
:class:`int`: x
157+
"""
158+
return P[0]
159+
160+
161+
def bytes_from_int(x):
162+
return int(x).to_bytes(32, byteorder='big')
163+
164+
165+
def int_from_bytes(b):
166+
return int.from_bytes(b, byteorder='big')
167+
168+
169+
def jacobi(x):
170+
return pow(x, (p - 1) // 2, p)
171+
172+
173+
def is_quad(x):
174+
return jacobi(x) == 1
175+
176+
177+
def hash_sha256(b):
178+
"""
179+
Args:
180+
b (:class:`bytes` or :class:`str`): sequence to be hashed
181+
Returns:
182+
:class:`bytes`: sha256 hash
183+
"""
184+
return hashlib.sha256(
185+
b if isinstance(b, bytes) else b.encode('utf-8')
186+
).digest()
187+
188+
189+
def point_mul(P, n):
190+
"""
191+
Multiply ``secp256k1`` point with scalar.
192+
Args:
193+
P (:class:`list`): ``secp256k1`` point
194+
n (:class:`int`): scalar
195+
Returns:
196+
:class:`list`: ``secp256k1`` point
197+
"""
198+
R = None
199+
for i in range(256):
200+
if ((n >> i) & 1):
201+
R = point_add(R, P)
202+
P = point_add(P, P)
203+
return R
204+
205+
206+
def y(P):
207+
"""
208+
Return :class:`P.y` or :class:`P[1]`.
209+
210+
Args:
211+
P (:class:`list`): ``secp256k1`` point
212+
Returns:
213+
:class:`int`: y
214+
"""
215+
return P[1]
216+
217+
218+
def encoded_from_point(P):
219+
"""
220+
Encode and compress a ``secp256k1`` point:
221+
* ``bytes(2) || bytes(x)`` if y is even
222+
* ``bytes(3) || bytes(x)`` if y is odd
223+
224+
Args:
225+
P (:class:`list`): ``secp256k1`` point
226+
Returns:
227+
:class:`bytes`: compressed and encoded point
228+
"""
229+
return (b'\x03' if y(P) & 1 else b'\x02') + bytes_from_int(x(P))
230+
231+
232+
# https://github.com/bcoin-org/bcrypto/blob/v4.1.0/lib/js/schnorr.js
233+
def bcrypto410_sign(msg, seckey0):
234+
if len(msg) != 32:
235+
raise ValueError('The message must be a 32-byte array.')
236+
237+
seckey = int_from_bytes(seckey0)
238+
if not (1 <= seckey <= n - 1):
239+
raise ValueError(
240+
'The secret key must be an integer in the range 1..n-1.'
241+
)
242+
243+
k0 = int_from_bytes(hash_sha256(seckey0 + msg)) % n
244+
if k0 == 0:
245+
raise RuntimeError(
246+
'Failure. This happens only with negligible probability.'
247+
)
248+
249+
R = G * k0
250+
Rraw = bytes_from_int(R.x)
251+
e = int_from_bytes(
252+
hash_sha256(Rraw + encoded_from_point(G*seckey) + msg)
253+
) % n
254+
255+
seckey %= n
256+
k0 %= n
257+
k = n - k0 if not is_quad(R.y) else k0
258+
259+
s = (k + e * seckey) % n
260+
s %= n
261+
262+
return Rraw + bytes_from_int(s)

0 commit comments

Comments
 (0)