Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

btclib-org / btclib / 691

29 Aug 2020 - 9:58 coverage: 99.891% (-0.1%) from 99.987%
691

Pull #45

travis-ci

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
Merge ea3bfdc5b into d9045bfc7
Pull Request #45: Assorted elliptic curve point multiplication (Sourcery refactored)

380 of 380 new or added lines in 4 files covered. (100.0%)

8 existing lines in 1 file now uncovered.

8212 of 8221 relevant lines covered (99.89%)

1.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.81
/btclib/tests/test_curves.py
1
#!/usr/bin/env python3
2

3
# Copyright (C) 2017-2020 The btclib developers
4
#
5
# This file is part of btclib. It is subject to the license terms in the
6
# LICENSE file found in the top-level directory of this distribution.
7
#
8
# No part of btclib including this file, may be copied, modified, propagated,
9
# or distributed except according to the terms contained in the LICENSE file.
10

11
"Tests for `btclib.curves` module."
1×
12

13
import secrets
1×
14
from typing import Dict
1×
15

16
import pytest
1×
17

18
from btclib.alias import INF, INFJ
1×
19
from btclib.curve import Curve, _jac_from_aff, _mult_aff, _mult_jac
1×
20
from btclib.curves import CURVES
1×
21
from btclib.numbertheory import mod_sqrt
1×
22

23
# FIXME Curve repr should use "dedbeef 00000000", not "0xdedbeef00000000"
24
# FIXME test curves when n>p
25

26

27
# test curves: very low cardinality
28
low_card_curves: Dict[str, Curve] = {}
1×
29
# 13 % 4 = 1; 13 % 8 = 5
30
low_card_curves["ec13_11"] = Curve(13, 7, 6, (1, 1), 11, 1, False)
1×
31
low_card_curves["ec13_19"] = Curve(13, 0, 2, (1, 9), 19, 1, False)
1×
32
# 17 % 4 = 1; 17 % 8 = 1
33
low_card_curves["ec17_13"] = Curve(17, 6, 8, (0, 12), 13, 2, False)
1×
34
low_card_curves["ec17_23"] = Curve(17, 3, 5, (1, 14), 23, 1, False)
1×
35
# 19 % 4 = 3; 19 % 8 = 3
36
low_card_curves["ec19_13"] = Curve(19, 0, 2, (4, 16), 13, 2, False)
1×
37
low_card_curves["ec19_23"] = Curve(19, 2, 9, (0, 16), 23, 1, False)
1×
38
# 23 % 4 = 3; 23 % 8 = 7
39
low_card_curves["ec23_19"] = Curve(23, 9, 7, (5, 4), 19, 1, False)
1×
40
low_card_curves["ec23_31"] = Curve(23, 5, 1, (0, 1), 31, 1, False)
1×
41

42
all_curves: Dict[str, Curve] = {}
1×
43
all_curves.update(low_card_curves)
1×
44
all_curves.update(CURVES)
1×
45

46

47
@pytest.mark.seventh
1×
48
def test_aff_jac_conversions() -> None:
1×
49
    for ec in all_curves.values():
1×
50

51
        # just a random point, not INF
52
        q = 1 + secrets.randbelow(ec.n - 1)
1×
53
        Q = _mult_aff(q, ec.G, ec)
1×
54
        QJ = _jac_from_aff(Q)
1×
55
        assert Q == ec._aff_from_jac(QJ)
1×
56
        x_Q = ec._x_aff_from_jac(QJ)
1×
57
        assert Q[0] == x_Q
1×
58

59
        assert INF == ec._aff_from_jac(_jac_from_aff(INF))
1×
60

61
        # relevant for BIP340-Schnorr signature verification
62
        assert not ec.has_square_y(INF)
1×
63
        with pytest.raises(ValueError, match="infinity point has no x-coordinate"):
1×
64
            ec._x_aff_from_jac(INFJ)
1×
65
        with pytest.raises(TypeError, match="not a point"):
1×
66
            ec.has_square_y("notapoint")  # type: ignore
1×
67

68

69
def test_add_aff() -> None:
1×
70
    for ec in all_curves.values():
1×
71

72
        # add G and the infinity point
73
        assert ec._add_aff(ec.G, INF) == ec.G
1×
74
        assert ec._add_aff(INF, ec.G) == ec.G
1×
75
        assert ec._add_aff(INF, INF) == INF
1×
76

77
        # add G and minus G
78
        assert ec._add_aff(ec.G, ec.negate(ec.G)) == INF
1×
79

80

81
def test_add_jac() -> None:
1×
82
    for ec in all_curves.values():
1×
83

84
        # add G and the infinity point
85
        assert ec._add_jac(ec.GJ, INFJ) == ec.GJ
1×
86
        assert ec._add_jac(INFJ, ec.GJ) == ec.GJ
1×
87
        assert ec._add_jac(INFJ, INFJ) == INFJ
1×
88

89
        # add G and minus G
90
        assert ec._add_jac(ec.GJ, ec.negate_jac(ec.GJ)) == INFJ
1×
91

92

93
@pytest.mark.eighth
1×
94
def test_add() -> None:
1×
95
    for ec in all_curves.values():
1×
96

97
        # just a random point, not INF
98
        q = 1 + secrets.randbelow(ec.n - 1)
1×
99
        Q = _mult_aff(q, ec.G, ec)
1×
100
        QJ = _jac_from_aff(Q)
1×
101

102
        # add Q and G
103
        R = ec._add_aff(Q, ec.G)
1×
104
        RJ = ec._add_jac(QJ, ec.GJ)
1×
105
        assert R == ec._aff_from_jac(RJ)
1×
106

107
        # double Q
108
        R = ec._add_aff(Q, Q)
1×
109
        RJ = ec._add_jac(QJ, QJ)
1×
110
        assert R == ec._aff_from_jac(RJ)
1×
111

112

113
@pytest.mark.fourth
1×
114
def test_ec_repr() -> None:
1×
115
    for ec in all_curves.values():
1×
116
        ec_repr = repr(ec)
1×
117
        if ec in low_card_curves.values() or ec.psize < 24:
1×
118
            ec_repr = ec_repr[:-1] + ", False)"
1×
119
        ec2 = eval(ec_repr)
1×
120
        assert str(ec) == str(ec2)
1×
121

122

123
@pytest.mark.sixth
1×
124
def test_is_on_curve() -> None:
1×
125
    for ec in all_curves.values():
1×
126

127
        with pytest.raises(ValueError, match="point must be a tuple"):
1×
128
            ec.is_on_curve("not a point")  # type: ignore
1×
129

130
        with pytest.raises(ValueError, match="x-coordinate not in 0..p-1: "):
1×
131
            ec.y(ec.p)
1×
132

133
        # just a random point, not INF
134
        q = 1 + secrets.randbelow(ec.n - 1)
1×
135
        Q = _mult_aff(q, ec.G, ec)
1×
136
        with pytest.raises(ValueError, match="y-coordinate not in 1..p-1: "):
1×
137
            ec.is_on_curve((Q[0], ec.p))
1×
138

139

140
def test_negate() -> None:
1×
141
    for ec in all_curves.values():
1×
142

143
        # just a random point, not INF
144
        q = 1 + secrets.randbelow(ec.n - 1)
1×
145
        Q = _mult_aff(q, ec.G, ec)
1×
146
        minus_Q = ec.negate(Q)
1×
147
        assert ec.add(Q, minus_Q) == INF
1×
148

149
        # Jacobian coordinates
150
        QJ = _jac_from_aff(Q)
1×
151
        minus_QJ = ec.negate_jac(QJ)
1×
152
        assert ec._add_jac(QJ, minus_QJ) == INFJ
1×
153

154
        # negate of INF is INF
155
        minus_INF = ec.negate(INF)
1×
156
        assert minus_INF == INF
1×
157

158
        # negate of INFJ is INFJ
159
        minus_INFJ = ec.negate_jac(INFJ)
1×
160
        assert minus_INFJ == INFJ
1×
161

162
    with pytest.raises(TypeError, match="not a point"):
1×
163
        ec.negate("notapoint")  # type: ignore
1×
164

165

166
def test_symmetry() -> None:
1×
167
    """Methods to break simmetry: quadratic residue, odd/even, low/high"""
168
    for ec in low_card_curves.values():
1×
169

170
        # just a random point, not INF
171
        q = 1 + secrets.randbelow(ec.n - 1)
1×
172
        Q = _mult_aff(q, ec.G, ec)
1×
173
        x_Q = Q[0]
1×
174

175
        y_odd = ec.y_odd(x_Q)
1×
176
        assert y_odd % 2 == 1
1×
177
        y_even = ec.y_odd(x_Q, False)
1×
178
        assert y_even % 2 == 0
1×
179
        assert y_even == ec.p - y_odd
1×
180

181
        y_low = ec.y_low(x_Q)
1×
182
        y_high = ec.y_low(x_Q, False)
1×
183
        assert y_low < y_high
1×
184
        assert y_high == ec.p - y_low
1×
185

186
        # compute quadratic residues
187
        hasRoot = {1}
1×
188
        for i in range(2, ec.p):
1×
189
            hasRoot.add(i * i % ec.p)
1×
190

191
        if ec.p % 4 == 3:
1×
192
            quad_res = ec.y_quadratic_residue(x_Q)
1×
193
            not_quad_res = ec.y_quadratic_residue(x_Q, False)
1×
194

195
            # in this case only quad_res is a quadratic residue
196
            assert quad_res in hasRoot
1×
197
            root = mod_sqrt(quad_res, ec.p)
1×
198
            assert quad_res == (root * root) % ec.p
1×
199
            root = ec.p - root
1×
200
            assert quad_res == (root * root) % ec.p
1×
201

202
            assert not_quad_res == ec.p - quad_res
1×
203
            assert not_quad_res not in hasRoot
1×
204
            with pytest.raises(ValueError, match="no root for "):
1×
205
                mod_sqrt(not_quad_res, ec.p)
1×
206
        else:
207
            assert ec.p % 4 == 1
1×
208
            # cannot use y_quadratic_residue in this case
209
            err_msg = "field prime is not equal to 3 mod 4: "
1×
210
            with pytest.raises(ValueError, match=err_msg):
1×
211
                ec.y_quadratic_residue(x_Q)
1×
212
            with pytest.raises(ValueError, match=err_msg):
1×
213
                ec.y_quadratic_residue(x_Q, False)
1×
214

215
            # in this case neither or both y_Q are quadratic residues
216
            neither = y_odd not in hasRoot and y_even not in hasRoot
1×
217
            both = y_odd in hasRoot and y_even in hasRoot
1×
218
            assert neither or both
1×
219
            if y_odd in hasRoot:  # both have roots
1×
UNCOV
220
                root = mod_sqrt(y_odd, ec.p)
!
UNCOV
221
                assert y_odd == (root * root) % ec.p
!
UNCOV
222
                root = ec.p - root
!
UNCOV
223
                assert y_odd == (root * root) % ec.p
!
UNCOV
224
                root = mod_sqrt(y_even, ec.p)
!
UNCOV
225
                assert y_even == (root * root) % ec.p
!
UNCOV
226
                root = ec.p - root
!
UNCOV
227
                assert y_even == (root * root) % ec.p
!
228
            else:
229
                err_msg = "no root for "
1×
230
                with pytest.raises(ValueError, match=err_msg):
1×
231
                    mod_sqrt(y_odd, ec.p)
1×
232
                with pytest.raises(ValueError, match=err_msg):
1×
233
                    mod_sqrt(y_even, ec.p)
1×
234

235
    # with the last curve
236
    with pytest.raises(ValueError, match="low1high0 must be bool or 1/0"):
1×
237
        ec.y_low(x_Q, 2)
1×
238
    with pytest.raises(ValueError, match="odd1even0 must be bool or 1/0"):
1×
239
        ec.y_odd(x_Q, 2)
1×
240
    with pytest.raises(ValueError, match="quad_res must be bool or 1/0"):
1×
241
        ec.y_quadratic_residue(x_Q, 2)
1×
242

243

244
@pytest.mark.fifth
1×
245
def test_mult_aff_curves() -> None:
1×
246
    for ec in all_curves.values():
1×
247
        assert _mult_aff(0, ec.G, ec) == INF
1×
248
        assert _mult_aff(0, INF, ec) == INF
1×
249

250
        assert _mult_aff(1, INF, ec) == INF
1×
251
        assert _mult_aff(1, ec.G, ec) == ec.G
1×
252

253
        P = ec._add_aff(ec.G, ec.G)
1×
254
        assert P == _mult_aff(2, ec.G, ec)
1×
255

256
        P = _mult_aff(ec.n - 1, ec.G, ec)
1×
257
        assert ec.negate(ec.G) == P
1×
258
        assert _mult_aff(ec.n - 1, INF, ec) == INF
1×
259

260
        assert ec._add_aff(P, ec.G) == INF
1×
261
        assert _mult_aff(ec.n, ec.G, ec) == INF
1×
262
        assert _mult_aff(ec.n, INF, ec) == INF
1×
263

264
        with pytest.raises(ValueError, match="negative m: -0x"):
1×
265
            _mult_aff(-1, ec.G, ec)
1×
266

267

268
def test_mult_jac_curves() -> None:
1×
269
    for ec in all_curves.values():
1×
270
        assert _mult_jac(0, ec.GJ, ec) == INFJ
1×
271
        assert _mult_jac(0, INFJ, ec) == INFJ
1×
272

273
        assert _mult_jac(1, INFJ, ec) == INFJ
1×
274
        assert _mult_jac(1, ec.GJ, ec) == ec.GJ
1×
275

276
        PJ = ec._add_jac(ec.GJ, ec.GJ)
1×
277
        assert PJ == _mult_jac(2, ec.GJ, ec)
1×
278

279
        PJ = _mult_jac(ec.n - 1, ec.GJ, ec)
1×
280
        assert ec._jac_equality(ec.negate_jac(ec.GJ), PJ)
1×
281

282
        assert _mult_jac(ec.n - 1, INFJ, ec) == INFJ
1×
283
        assert ec._add_jac(PJ, ec.GJ) == INFJ
1×
284
        assert _mult_jac(ec.n, ec.GJ, ec) == INFJ
1×
285

286
        with pytest.raises(ValueError, match="negative m: -0x"):
1×
287
            _mult_jac(-1, ec.GJ, ec)
1×
288

289

290
def test_mult() -> None:
1×
291
    for ec in low_card_curves.values():
1×
292
        for q in range(ec.n):
1×
293
            Q = _mult_aff(q, ec.G, ec)
1×
294
            QJ = _mult_jac(q, ec.GJ, ec)
1×
295
            assert Q == ec._aff_from_jac(QJ)
1×
296
        assert INF == _mult_aff(q, INF, ec)
1×
297
        assert INFJ == _mult_jac(q, INFJ, ec)
1×
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2023 Coveralls, Inc