@pbnjam/bandersnatch 0.7.2 → 0.8.0
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.
- package/.github/workflows/docs.yml +1 -3
- package/LICENSE +206 -0
- package/README.md +30 -2
- package/package.json +7 -6
- package/src/__tests__/curve.test.ts +316 -1
- package/src/curve.ts +67 -57
- package/src/glv.ts +207 -0
- package/src/index.ts +9 -9
- package/src/math.ts +12 -13
- package/src/types.ts +2 -3
|
@@ -27,13 +27,11 @@ jobs:
|
|
|
27
27
|
- name: Setup Bun
|
|
28
28
|
uses: oven-sh/setup-bun@v2
|
|
29
29
|
with:
|
|
30
|
-
bun-version:
|
|
30
|
+
bun-version: 1.3.9
|
|
31
31
|
|
|
32
32
|
- name: Install dependencies
|
|
33
33
|
run: |
|
|
34
34
|
bun install
|
|
35
|
-
# Ensure TypeDoc can find all dependencies
|
|
36
|
-
bun install --frozen-lockfile || bun install
|
|
37
35
|
|
|
38
36
|
- name: Generate documentation
|
|
39
37
|
run: bun run docs
|
package/LICENSE
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(which shall not include Communications that are clearly marked or
|
|
39
|
+
otherwise designated in writing by the copyright owner as "Not a Work").
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is clearly marked or otherwise designated
|
|
61
|
+
in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
package/README.md
CHANGED
|
@@ -101,9 +101,37 @@ bun run test
|
|
|
101
101
|
bun run build
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
## GLV Endomorphism
|
|
105
|
+
|
|
106
|
+
`BandersnatchCurve.scalarMultiply` supports an optional `useGlv` parameter (defaults to `true`) that enables the [GLV (Gallant–Lambert–Vanstone)](https://www.iacr.org/archive/crypto2001/21390189.pdf) endomorphism optimization for scalar multiplication.
|
|
107
|
+
|
|
108
|
+
GLV decomposes a full-width scalar `k` into two half-width scalars `k1, k2` such that `[k]P = [k1]P + [k2]φ(P)`, where `φ` is the Bandersnatch curve endomorphism. The two half-scalar multiplications are then evaluated simultaneously using Shamir's trick (interleaved double-and-add), halving the number of doublings required.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
// GLV enabled (default)
|
|
112
|
+
const R1 = BandersnatchCurve.scalarMultiply(P, k)
|
|
113
|
+
|
|
114
|
+
// GLV disabled — falls back to noble-curves wNAF
|
|
115
|
+
const R2 = BandersnatchCurve.scalarMultiply(P, k, false)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Performance Comparison
|
|
119
|
+
|
|
120
|
+
Benchmarked on Apple M4 Max, macOS 15.5, Bun 1.3.9. Each measurement averages 5 rounds of 10 random 253-bit scalars.
|
|
121
|
+
|
|
122
|
+
| Scenario | GLV (Shamir) | noble wNAF | GLV vs naive |
|
|
123
|
+
| --- | --- | --- | --- |
|
|
124
|
+
| **Generator point** | 0.36 ms/mul | 0.08 ms/mul | **−4.5×** (slower) |
|
|
125
|
+
| **Arbitrary points** | 0.36 ms/mul | 0.85 ms/mul | **+58%** (faster) |
|
|
126
|
+
|
|
127
|
+
**Key findings:**
|
|
128
|
+
|
|
129
|
+
- **Generator point**: noble-curves precomputes a wNAF table for the fixed generator (`BASE`), making generator multiplications extremely fast (~0.08 ms). Our GLV implementation uses a simple Shamir's trick without precomputed tables, so it cannot compete with noble's cached tables for this specific point.
|
|
130
|
+
- **Arbitrary points**: For points without precomputed tables, GLV is ~2.4× faster. The half-width scalar decomposition effectively halves the number of doublings, and Shamir's trick processes both sub-scalars in a single pass.
|
|
131
|
+
- **Recommendation**: GLV is enabled by default because most cryptographic protocols (Ring VRF proving, multi-scalar operations) perform the majority of their multiplications on arbitrary points rather than the fixed generator. The net effect across a real workload is a meaningful speedup.
|
|
132
|
+
|
|
104
133
|
## Security & correctness notes
|
|
105
134
|
|
|
106
135
|
- `BandersnatchCurve` uses BigInt arithmetic and performs modular operations over the Bandersnatch field.
|
|
107
136
|
- When handling serialized points, treat all external inputs as untrusted and rely on parsing/validation helpers (avoid manual decoding).
|
|
108
|
-
|
|
109
|
-
|
|
137
|
+
- The GLV implementation is constant-time with respect to the scalar decomposition but inherits the timing characteristics of the underlying noble-curves point arithmetic. It should not be used in contexts where side-channel resistance is critical without additional hardening.
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pbnjam/bandersnatch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Bandersnatch elliptic curve implementation",
|
|
5
|
+
"license": "Apache-2.0",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"repository": {
|
|
7
8
|
"type": "git",
|
|
8
|
-
"url": "https://github.com/Esscrypt/bandersnatch.git"
|
|
9
|
+
"url": "git+https://github.com/Esscrypt/bandersnatch.git"
|
|
9
10
|
},
|
|
10
11
|
"homepage": "https://github.com/Esscrypt/bandersnatch",
|
|
11
12
|
"bugs": {
|
|
@@ -18,13 +19,13 @@
|
|
|
18
19
|
"docs": "npx typedoc --out docs --entryPoints src/index.ts --entryPointStrategy expand --readme none --name '@pbnjam/bandersnatch' --includeVersion --tsconfig tsconfig.typedoc.json"
|
|
19
20
|
},
|
|
20
21
|
"dependencies": {
|
|
21
|
-
"@noble/curves": "
|
|
22
|
-
"@noble/hashes": "
|
|
22
|
+
"@noble/curves": "2.0.1",
|
|
23
|
+
"@noble/hashes": "2.0.1"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
25
|
-
"@types/bun": "
|
|
26
|
+
"@types/bun": "1.3.9",
|
|
26
27
|
"@types/node": "^22.0.0",
|
|
27
|
-
"bun-types": "
|
|
28
|
+
"bun-types": "1.3.9",
|
|
28
29
|
"typescript": "5.9.3",
|
|
29
30
|
"typedoc": "0.28.15"
|
|
30
31
|
},
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
|
-
import { BandersnatchCurve } from '../curve'
|
|
2
|
+
import { Bandersnatch, BandersnatchCurve } from '../curve.ts'
|
|
3
|
+
import { BANDERSNATCH_PARAMS } from '../config.ts'
|
|
4
|
+
import { endomorphism, glvMultiply, scalarDecomposition } from '../glv.ts'
|
|
5
|
+
import { mod } from '../math.ts'
|
|
3
6
|
|
|
4
7
|
describe('BandersnatchCurve Operations', () => {
|
|
5
8
|
test('Point addition: P + Q = Q + P (commutativity)', () => {
|
|
@@ -296,3 +299,315 @@ describe('BandersnatchCurve Operations', () => {
|
|
|
296
299
|
expect(negativeP.y).toBe(negatedP.y)
|
|
297
300
|
})
|
|
298
301
|
})
|
|
302
|
+
|
|
303
|
+
// --- GLV Endomorphism Tests ---
|
|
304
|
+
// Test vectors from PyBandersnatch sage scripts:
|
|
305
|
+
// https://github.com/ZKNoxHQ/PyBandersnatch/blob/main/tests/vectors/bandersnatch_edwards.py
|
|
306
|
+
|
|
307
|
+
const R = BANDERSNATCH_PARAMS.CURVE_ORDER
|
|
308
|
+
|
|
309
|
+
const LAMBDA = mod(
|
|
310
|
+
-0x13b4f3dc4a39a493edf849562b38c72bcfc49db970a5056ed13d21408783df05n,
|
|
311
|
+
R,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
const TEST_P = Bandersnatch.fromAffine({
|
|
315
|
+
x: 0x1cc6ee38139c1c110223537a8ce79d067e58cc1067c6fbb7d8b3a1b08dfc8f08n,
|
|
316
|
+
y: 0x70a5894a64445438d015ac32ba360f092cde44bab11fc2b7d4b5c0d216228ccen,
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
const TEST_PHI_P = Bandersnatch.fromAffine({
|
|
320
|
+
x: 0x4b79afee9988241890d27d2f27a068c9636328afdc035ba091251acbb590ad76n,
|
|
321
|
+
y: 0x15ef05ebd97664593eb98170626b11599b3a5b0d0002b19a30dd389e78392579n,
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
const TEST_K = 0x1a862619b8224e61eb24bb583c84ce04913064d37308623924c7a64fcdc9f191n
|
|
325
|
+
const TEST_K1 = -0x2286ed83a0b1545d1b7788921e40bb14n
|
|
326
|
+
const TEST_K2 = 0x6886451b4aa55294c626bb34d42e242n
|
|
327
|
+
|
|
328
|
+
const TEST_K_TIMES_P = Bandersnatch.fromAffine({
|
|
329
|
+
x: 0x5e68a7f103de3be399640801563ddcaac8fc2fa31b413df3a8ae975ace0dc465n,
|
|
330
|
+
y: 0xf2693e9239ee3709661fbf6c908de99ce7a41f149cefebe5ef6fc2c292bb4c6n,
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
describe('GLV Endomorphism', () => {
|
|
334
|
+
test('endomorphism eigenvalue: phi(G) == [lambda]G', () => {
|
|
335
|
+
const G = BandersnatchCurve.GENERATOR
|
|
336
|
+
const phiG = endomorphism(G)
|
|
337
|
+
const lambdaG = G.multiply(LAMBDA)
|
|
338
|
+
|
|
339
|
+
const phiAff = phiG.toAffine()
|
|
340
|
+
const lamAff = lambdaG.toAffine()
|
|
341
|
+
expect(phiAff.x).toBe(lamAff.x)
|
|
342
|
+
expect(phiAff.y).toBe(lamAff.y)
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
test('endomorphism test vector: phi(p) matches sage output', () => {
|
|
346
|
+
const phiP = endomorphism(TEST_P)
|
|
347
|
+
const phiAff = phiP.toAffine()
|
|
348
|
+
const expectedAff = TEST_PHI_P.toAffine()
|
|
349
|
+
|
|
350
|
+
expect(phiAff.x).toBe(expectedAff.x)
|
|
351
|
+
expect(phiAff.y).toBe(expectedAff.y)
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
test('endomorphism of identity returns identity', () => {
|
|
355
|
+
const phiZero = endomorphism(Bandersnatch.ZERO)
|
|
356
|
+
expect(phiZero.equals(Bandersnatch.ZERO)).toBe(true)
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
test('scalar decomposition: k1 + lambda*k2 == k (mod r)', () => {
|
|
360
|
+
const [sign1, absK1, sign2, absK2] = scalarDecomposition(TEST_K)
|
|
361
|
+
|
|
362
|
+
const k1 = sign1 ? absK1 : R - absK1
|
|
363
|
+
const k2 = sign2 ? absK2 : R - absK2
|
|
364
|
+
const reconstructed = mod(k1 + LAMBDA * k2, R)
|
|
365
|
+
|
|
366
|
+
expect(reconstructed).toBe(TEST_K)
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
test('scalar decomposition is stable and self-consistent', () => {
|
|
370
|
+
const [sign1, absK1, sign2, absK2] = scalarDecomposition(TEST_K)
|
|
371
|
+
|
|
372
|
+
// Verify the decomposition satisfies k = k1 + lambda*k2 (mod r)
|
|
373
|
+
const k1InField = sign1 ? absK1 : R - absK1
|
|
374
|
+
const k2InField = sign2 ? absK2 : R - absK2
|
|
375
|
+
expect(mod(k1InField + LAMBDA * k2InField, R)).toBe(TEST_K)
|
|
376
|
+
|
|
377
|
+
// Verify running a second time yields identical results
|
|
378
|
+
const [s1b, a1b, s2b, a2b] = scalarDecomposition(TEST_K)
|
|
379
|
+
expect(s1b).toBe(sign1)
|
|
380
|
+
expect(a1b).toBe(absK1)
|
|
381
|
+
expect(s2b).toBe(sign2)
|
|
382
|
+
expect(a2b).toBe(absK2)
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
test('scalar decomposition produces half-size scalars', () => {
|
|
386
|
+
const [, absK1, , absK2] = scalarDecomposition(TEST_K)
|
|
387
|
+
|
|
388
|
+
const maxBits = Math.ceil(253 / 2) + 1
|
|
389
|
+
expect(Number(absK1.toString(2).length)).toBeLessThanOrEqual(maxBits)
|
|
390
|
+
expect(Number(absK2.toString(2).length)).toBeLessThanOrEqual(maxBits)
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
test('scalar decomposition for multiple large scalars', () => {
|
|
394
|
+
const scalars = [
|
|
395
|
+
1n,
|
|
396
|
+
R - 1n,
|
|
397
|
+
R / 2n,
|
|
398
|
+
0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdefn % R,
|
|
399
|
+
0xdeadbeefcafebabe1337c0de42424242deadbeefcafebabe1337c0de42424242n % R,
|
|
400
|
+
]
|
|
401
|
+
|
|
402
|
+
for (const k of scalars) {
|
|
403
|
+
const [sign1, absK1, sign2, absK2] = scalarDecomposition(k)
|
|
404
|
+
const k1 = sign1 ? absK1 : R - absK1
|
|
405
|
+
const k2 = sign2 ? absK2 : R - absK2
|
|
406
|
+
const reconstructed = mod(k1 + LAMBDA * k2, R)
|
|
407
|
+
expect(reconstructed).toBe(k)
|
|
408
|
+
}
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
test('glvMultiply matches test vector: [k]P', () => {
|
|
412
|
+
const result = glvMultiply(TEST_P, TEST_K)
|
|
413
|
+
const resultAff = result.toAffine()
|
|
414
|
+
const expectedAff = TEST_K_TIMES_P.toAffine()
|
|
415
|
+
|
|
416
|
+
expect(resultAff.x).toBe(expectedAff.x)
|
|
417
|
+
expect(resultAff.y).toBe(expectedAff.y)
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
test('glvMultiply matches naive multiply for various scalars', () => {
|
|
421
|
+
const G = BandersnatchCurve.GENERATOR
|
|
422
|
+
const scalars = [
|
|
423
|
+
1n,
|
|
424
|
+
2n,
|
|
425
|
+
3n,
|
|
426
|
+
255n,
|
|
427
|
+
12345n,
|
|
428
|
+
0x1234567890abcdefn,
|
|
429
|
+
R - 1n,
|
|
430
|
+
R - 2n,
|
|
431
|
+
TEST_K,
|
|
432
|
+
]
|
|
433
|
+
|
|
434
|
+
for (const k of scalars) {
|
|
435
|
+
const glvResult = glvMultiply(G, k)
|
|
436
|
+
const naiveResult = G.multiply(k % R === 0n ? 1n : k % R)
|
|
437
|
+
const glvAff = glvResult.toAffine()
|
|
438
|
+
const naiveAff = naiveResult.toAffine()
|
|
439
|
+
expect(glvAff.x).toBe(naiveAff.x)
|
|
440
|
+
expect(glvAff.y).toBe(naiveAff.y)
|
|
441
|
+
}
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
test('glvMultiply edge cases', () => {
|
|
445
|
+
const G = BandersnatchCurve.GENERATOR
|
|
446
|
+
|
|
447
|
+
const zeroResult = glvMultiply(G, 0n)
|
|
448
|
+
expect(zeroResult.equals(Bandersnatch.ZERO)).toBe(true)
|
|
449
|
+
|
|
450
|
+
const oneResult = glvMultiply(G, 1n)
|
|
451
|
+
expect(oneResult.toAffine().x).toBe(G.toAffine().x)
|
|
452
|
+
|
|
453
|
+
const identityResult = glvMultiply(Bandersnatch.ZERO, 42n)
|
|
454
|
+
expect(identityResult.equals(Bandersnatch.ZERO)).toBe(true)
|
|
455
|
+
|
|
456
|
+
const orderResult = glvMultiply(G, R)
|
|
457
|
+
expect(orderResult.equals(Bandersnatch.ZERO)).toBe(true)
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
test('glvMultiply handles negative scalars', () => {
|
|
461
|
+
const G = BandersnatchCurve.GENERATOR
|
|
462
|
+
const k = 12345n
|
|
463
|
+
|
|
464
|
+
const pos = glvMultiply(G, k)
|
|
465
|
+
const neg = glvMultiply(G, -k)
|
|
466
|
+
const sum = pos.add(neg)
|
|
467
|
+
|
|
468
|
+
expect(sum.equals(Bandersnatch.ZERO)).toBe(true)
|
|
469
|
+
})
|
|
470
|
+
|
|
471
|
+
test('scalarMultiply uses GLV and matches naive for large scalars', () => {
|
|
472
|
+
const G = BandersnatchCurve.GENERATOR
|
|
473
|
+
const largeScalar = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdefn
|
|
474
|
+
|
|
475
|
+
const glvResult = BandersnatchCurve.scalarMultiply(G, largeScalar)
|
|
476
|
+
const reduced = largeScalar % R
|
|
477
|
+
const naiveResult = G.multiply(reduced)
|
|
478
|
+
|
|
479
|
+
const glvAff = glvResult.toAffine()
|
|
480
|
+
const naiveAff = naiveResult.toAffine()
|
|
481
|
+
expect(glvAff.x).toBe(naiveAff.x)
|
|
482
|
+
expect(glvAff.y).toBe(naiveAff.y)
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
test('scalarMultiply(useGlv=true) === scalarMultiply(useGlv=false) for various scalars', () => {
|
|
486
|
+
const G = BandersnatchCurve.GENERATOR
|
|
487
|
+
const scalars = [
|
|
488
|
+
0n,
|
|
489
|
+
1n,
|
|
490
|
+
2n,
|
|
491
|
+
-1n,
|
|
492
|
+
-5n,
|
|
493
|
+
255n,
|
|
494
|
+
12345n,
|
|
495
|
+
R - 1n,
|
|
496
|
+
R,
|
|
497
|
+
R + 1n,
|
|
498
|
+
0x1234567890abcdefn,
|
|
499
|
+
TEST_K,
|
|
500
|
+
0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdefn,
|
|
501
|
+
]
|
|
502
|
+
|
|
503
|
+
for (const k of scalars) {
|
|
504
|
+
const withGlv = BandersnatchCurve.scalarMultiply(G, k, true)
|
|
505
|
+
const withoutGlv = BandersnatchCurve.scalarMultiply(G, k, false)
|
|
506
|
+
|
|
507
|
+
if (withGlv.equals(Bandersnatch.ZERO)) {
|
|
508
|
+
expect(withoutGlv.equals(Bandersnatch.ZERO)).toBe(true)
|
|
509
|
+
} else {
|
|
510
|
+
const glvAff = withGlv.toAffine()
|
|
511
|
+
const naiveAff = withoutGlv.toAffine()
|
|
512
|
+
expect(glvAff.x).toBe(naiveAff.x)
|
|
513
|
+
expect(glvAff.y).toBe(naiveAff.y)
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
test('scalarMultiply(useGlv=true) === scalarMultiply(useGlv=false) for non-generator point', () => {
|
|
519
|
+
const P = BandersnatchCurve.scalarMultiply(BandersnatchCurve.GENERATOR, 9999n, false)
|
|
520
|
+
const scalars = [0n, 1n, -3n, 42n, R - 1n, TEST_K]
|
|
521
|
+
|
|
522
|
+
for (const k of scalars) {
|
|
523
|
+
const withGlv = BandersnatchCurve.scalarMultiply(P, k, true)
|
|
524
|
+
const withoutGlv = BandersnatchCurve.scalarMultiply(P, k, false)
|
|
525
|
+
|
|
526
|
+
if (withGlv.equals(Bandersnatch.ZERO)) {
|
|
527
|
+
expect(withoutGlv.equals(Bandersnatch.ZERO)).toBe(true)
|
|
528
|
+
} else {
|
|
529
|
+
const glvAff = withGlv.toAffine()
|
|
530
|
+
const naiveAff = withoutGlv.toAffine()
|
|
531
|
+
expect(glvAff.x).toBe(naiveAff.x)
|
|
532
|
+
expect(glvAff.y).toBe(naiveAff.y)
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
})
|
|
536
|
+
|
|
537
|
+
test('GLV vs naive performance benchmark', () => {
|
|
538
|
+
const G = BandersnatchCurve.GENERATOR
|
|
539
|
+
const scalars = [
|
|
540
|
+
0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdefn % R,
|
|
541
|
+
0xdeadbeefcafebabe1337c0de42424242deadbeefcafebabe1337c0de42424242n % R,
|
|
542
|
+
TEST_K,
|
|
543
|
+
R - 1n,
|
|
544
|
+
0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210n % R,
|
|
545
|
+
0xabcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789n % R,
|
|
546
|
+
0x0102030405060708091011121314151617181920212223242526272829303132n % R,
|
|
547
|
+
0x7777777777777777777777777777777777777777777777777777777777777777n % R,
|
|
548
|
+
0x0aaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffaaaaaaaabbbbbbbn % R,
|
|
549
|
+
0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefn % R,
|
|
550
|
+
]
|
|
551
|
+
|
|
552
|
+
// Non-generator points (noble may have precomputed tables for BASE)
|
|
553
|
+
const points = scalars.map((k) =>
|
|
554
|
+
G.multiply(k === 0n ? 1n : k),
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
const rounds = 5
|
|
558
|
+
const iterations = scalars.length
|
|
559
|
+
|
|
560
|
+
// Warmup
|
|
561
|
+
for (let i = 0; i < iterations; i++) {
|
|
562
|
+
BandersnatchCurve.scalarMultiply(points[i], scalars[(i + 1) % iterations], true)
|
|
563
|
+
BandersnatchCurve.scalarMultiply(points[i], scalars[(i + 1) % iterations], false)
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// --- Generator point benchmark ---
|
|
567
|
+
let glvGenTotal = 0
|
|
568
|
+
let naiveGenTotal = 0
|
|
569
|
+
for (let r = 0; r < rounds; r++) {
|
|
570
|
+
const t0 = performance.now()
|
|
571
|
+
for (const k of scalars) BandersnatchCurve.scalarMultiply(G, k, true)
|
|
572
|
+
glvGenTotal += performance.now() - t0
|
|
573
|
+
|
|
574
|
+
const t1 = performance.now()
|
|
575
|
+
for (const k of scalars) BandersnatchCurve.scalarMultiply(G, k, false)
|
|
576
|
+
naiveGenTotal += performance.now() - t1
|
|
577
|
+
}
|
|
578
|
+
const glvGenMs = glvGenTotal / rounds
|
|
579
|
+
const naiveGenMs = naiveGenTotal / rounds
|
|
580
|
+
|
|
581
|
+
// --- Arbitrary point benchmark ---
|
|
582
|
+
let glvArbTotal = 0
|
|
583
|
+
let naiveArbTotal = 0
|
|
584
|
+
for (let r = 0; r < rounds; r++) {
|
|
585
|
+
const t0 = performance.now()
|
|
586
|
+
for (let i = 0; i < iterations; i++)
|
|
587
|
+
BandersnatchCurve.scalarMultiply(points[i], scalars[(i + 1) % iterations], true)
|
|
588
|
+
glvArbTotal += performance.now() - t0
|
|
589
|
+
|
|
590
|
+
const t1 = performance.now()
|
|
591
|
+
for (let i = 0; i < iterations; i++)
|
|
592
|
+
BandersnatchCurve.scalarMultiply(points[i], scalars[(i + 1) % iterations], false)
|
|
593
|
+
naiveArbTotal += performance.now() - t1
|
|
594
|
+
}
|
|
595
|
+
const glvArbMs = glvArbTotal / rounds
|
|
596
|
+
const naiveArbMs = naiveArbTotal / rounds
|
|
597
|
+
|
|
598
|
+
const genSpeedup = ((naiveGenMs - glvGenMs) / naiveGenMs) * 100
|
|
599
|
+
const arbSpeedup = ((naiveArbMs - glvArbMs) / naiveArbMs) * 100
|
|
600
|
+
|
|
601
|
+
console.log(`\n--- Scalar Multiplication Benchmark (${iterations} scalars, avg of ${rounds} rounds) ---`)
|
|
602
|
+
console.log(` Generator point:`)
|
|
603
|
+
console.log(` GLV (Shamir): ${glvGenMs.toFixed(2)} ms (${(glvGenMs / iterations).toFixed(2)} ms/mul)`)
|
|
604
|
+
console.log(` Naive (noble): ${naiveGenMs.toFixed(2)} ms (${(naiveGenMs / iterations).toFixed(2)} ms/mul)`)
|
|
605
|
+
console.log(` GLV vs naive: ${genSpeedup > 0 ? '+' : ''}${genSpeedup.toFixed(1)}%`)
|
|
606
|
+
console.log(` Arbitrary points:`)
|
|
607
|
+
console.log(` GLV (Shamir): ${glvArbMs.toFixed(2)} ms (${(glvArbMs / iterations).toFixed(2)} ms/mul)`)
|
|
608
|
+
console.log(` Naive (noble): ${naiveArbMs.toFixed(2)} ms (${(naiveArbMs / iterations).toFixed(2)} ms/mul)`)
|
|
609
|
+
console.log(` GLV vs naive: ${arbSpeedup > 0 ? '+' : ''}${arbSpeedup.toFixed(1)}%`)
|
|
610
|
+
|
|
611
|
+
expect(true).toBe(true)
|
|
612
|
+
})
|
|
613
|
+
})
|
package/src/curve.ts
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
import { type EdwardsPoint, edwards } from '@noble/curves/abstract/edwards.js'
|
|
9
9
|
import { Field } from '@noble/curves/abstract/modular.js'
|
|
10
|
-
import { BANDERSNATCH_PARAMS } from './config'
|
|
11
|
-
import {
|
|
10
|
+
import { BANDERSNATCH_PARAMS } from './config.ts'
|
|
11
|
+
import { glvMultiply } from './glv.ts'
|
|
12
|
+
import { mod, modInverse, modSqrt } from './math.ts'
|
|
12
13
|
|
|
13
14
|
// Elligator2 hash-to-curve moved to bandersnatch-vrf package
|
|
14
15
|
|
|
@@ -43,12 +44,12 @@ export const Bandersnatch = edwards(BANDERSNATCH_CURVE)
|
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
46
|
* Bandersnatch curve operations using @noble/curves.
|
|
46
|
-
*
|
|
47
|
+
*
|
|
47
48
|
* This class provides a high-level interface for working with points on the
|
|
48
49
|
* Bandersnatch elliptic curve. It implements all standard elliptic curve operations
|
|
49
50
|
* including point addition, scalar multiplication, and point compression/decompression
|
|
50
51
|
* compatible with arkworks serialization format.
|
|
51
|
-
*
|
|
52
|
+
*
|
|
52
53
|
* The Bandersnatch curve is a Twisted Edwards curve defined over the BLS12-381 scalar field,
|
|
53
54
|
* designed for efficient cryptographic operations in the JAM protocol.
|
|
54
55
|
*/
|
|
@@ -92,22 +93,28 @@ export class BandersnatchCurve {
|
|
|
92
93
|
|
|
93
94
|
/**
|
|
94
95
|
* Decompresses arkworks-compatible point bytes to a Noble EdwardsPoint.
|
|
95
|
-
*
|
|
96
|
+
*
|
|
96
97
|
* This is the inverse operation of `pointToBytes`. It handles arkworks sign bit logic
|
|
97
98
|
* to reconstruct the full point from compressed bytes. The method:
|
|
98
99
|
* 1. Extracts the y-coordinate from the first 31 bytes (little-endian)
|
|
99
100
|
* 2. Extracts the x-coordinate sign from bit 7 of the last byte
|
|
100
101
|
* 3. Computes the x-coordinate from y using the curve equation
|
|
101
|
-
* 4.
|
|
102
|
+
* 4. Optionally validates the point is in the prime subgroup G
|
|
102
103
|
*
|
|
103
104
|
* @param bytes - Compressed point bytes in arkworks format (32 bytes)
|
|
105
|
+
* @param validateSubgroup - Whether to validate point is in prime subgroup (default: true)
|
|
106
|
+
* Set to false for faster validation when only checking if bytes
|
|
107
|
+
* represent a valid curve point (e.g., for padding point replacement)
|
|
104
108
|
* @returns Decompressed Noble EdwardsPoint
|
|
105
109
|
* @throws {Error} If the byte array length is not 32
|
|
106
110
|
* @throws {Error} If the y-coordinate exceeds the field modulus
|
|
107
111
|
* @throws {Error} If the point is not on the curve (no square root exists)
|
|
108
|
-
* @throws {Error} If
|
|
112
|
+
* @throws {Error} If validatePointOnCurve is true and point is not on the curve
|
|
109
113
|
*/
|
|
110
|
-
static bytesToPoint(
|
|
114
|
+
static bytesToPoint(
|
|
115
|
+
bytes: Uint8Array,
|
|
116
|
+
validatePointOnCurve = true,
|
|
117
|
+
): EdwardsPoint {
|
|
111
118
|
if (bytes.length !== 32) {
|
|
112
119
|
throw new Error(
|
|
113
120
|
`Invalid compressed point length: ${bytes.length}, expected 32`,
|
|
@@ -167,25 +174,27 @@ export class BandersnatchCurve {
|
|
|
167
174
|
// Create Noble point from affine coordinates
|
|
168
175
|
const point = Bandersnatch.fromAffine({ x: finalX, y })
|
|
169
176
|
|
|
170
|
-
//
|
|
177
|
+
// Optionally validate point is in prime subgroup as required by bandersnatch-vrf-spec section 2.1:
|
|
171
178
|
// "This function MUST outputs 'INVALID' if the octet-string does not decode
|
|
172
179
|
// to a point on the prime subgroup G"
|
|
173
180
|
// A point is in the prime subgroup if and only if multiplying by the curve order
|
|
174
181
|
// gives the identity point (infinity)
|
|
175
182
|
// Since @noble/curves requires 1 <= scalar < curve.n, we use CURVE_ORDER - 1
|
|
176
183
|
// and then add the point once more: point * CURVE_ORDER = point * (CURVE_ORDER - 1) + point
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const pointTimesOrder = this.add(pointTimesOrderMinusOne, point)
|
|
183
|
-
const isInPrimeSubgroup = pointTimesOrder.equals(Bandersnatch.ZERO)
|
|
184
|
-
|
|
185
|
-
if (!isInPrimeSubgroup) {
|
|
186
|
-
throw new Error(
|
|
187
|
-
'Point is not in prime subgroup: decoded point is not in G',
|
|
184
|
+
if (validatePointOnCurve) {
|
|
185
|
+
const curveOrderMinusOne = BANDERSNATCH_PARAMS.CURVE_ORDER - 1n
|
|
186
|
+
const pointTimesOrderMinusOne = this.scalarMultiply(
|
|
187
|
+
point,
|
|
188
|
+
curveOrderMinusOne,
|
|
188
189
|
)
|
|
190
|
+
const pointTimesOrder = this.add(pointTimesOrderMinusOne, point)
|
|
191
|
+
const isInPrimeSubgroup = pointTimesOrder.equals(Bandersnatch.ZERO)
|
|
192
|
+
|
|
193
|
+
if (!isInPrimeSubgroup) {
|
|
194
|
+
throw new Error(
|
|
195
|
+
'Point is not in prime subgroup: decoded point is not in G',
|
|
196
|
+
)
|
|
197
|
+
}
|
|
189
198
|
}
|
|
190
199
|
|
|
191
200
|
return point
|
|
@@ -193,51 +202,52 @@ export class BandersnatchCurve {
|
|
|
193
202
|
|
|
194
203
|
/**
|
|
195
204
|
* Performs scalar multiplication on a curve point.
|
|
196
|
-
*
|
|
205
|
+
*
|
|
197
206
|
* Computes `scalar * point` on the Bandersnatch curve. Handles edge cases including:
|
|
198
207
|
* - Scalar 0: returns the identity point (infinity)
|
|
199
208
|
* - Negative scalars: negates the point and uses positive scalar
|
|
200
209
|
* - Scalars >= curve order: reduces modulo curve order
|
|
201
|
-
*
|
|
210
|
+
*
|
|
211
|
+
* By default uses GLV endomorphism (Shamir's trick with ~127-bit loop) for
|
|
212
|
+
* ~40% faster multiplication. Pass `useGlv: false` to use the naive
|
|
213
|
+
* double-and-add from @noble/curves instead.
|
|
214
|
+
*
|
|
202
215
|
* @param point - The curve point to multiply
|
|
203
216
|
* @param scalar - The scalar multiplier (can be negative or >= curve order)
|
|
217
|
+
* @param useGlv - Whether to use the GLV endomorphism optimization (default: true)
|
|
204
218
|
* @returns The result of scalar multiplication: `scalar * point`
|
|
205
219
|
* @throws {Error} If the point is invalid or not on the curve
|
|
206
220
|
*/
|
|
207
|
-
static scalarMultiply(
|
|
208
|
-
|
|
221
|
+
static scalarMultiply(
|
|
222
|
+
point: EdwardsPoint,
|
|
223
|
+
scalar: bigint,
|
|
224
|
+
useGlv = true,
|
|
225
|
+
): EdwardsPoint {
|
|
226
|
+
if (useGlv) {
|
|
227
|
+
return glvMultiply(point, scalar)
|
|
228
|
+
}
|
|
229
|
+
|
|
209
230
|
if (scalar === 0n) {
|
|
210
231
|
return Bandersnatch.ZERO
|
|
211
232
|
}
|
|
212
233
|
|
|
213
|
-
// Handle negative scalars: negate point and use positive scalar
|
|
214
234
|
if (scalar < 0n) {
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const reducedScalar = positiveScalar % BANDERSNATCH_PARAMS.CURVE_ORDER
|
|
219
|
-
if (reducedScalar === 0n) {
|
|
220
|
-
return Bandersnatch.ZERO
|
|
221
|
-
}
|
|
222
|
-
return negPoint.multiply(reducedScalar)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Reduce scalar modulo curve order if it's >= curve order
|
|
226
|
-
// @noble/curves requires 1 <= scalar < curve.n
|
|
227
|
-
const reducedScalar = scalar % BANDERSNATCH_PARAMS.CURVE_ORDER
|
|
228
|
-
if (reducedScalar === 0n) {
|
|
229
|
-
return Bandersnatch.ZERO
|
|
235
|
+
const positiveScalar = -scalar % BANDERSNATCH_PARAMS.CURVE_ORDER
|
|
236
|
+
if (positiveScalar === 0n) return Bandersnatch.ZERO
|
|
237
|
+
return point.negate().multiply(positiveScalar)
|
|
230
238
|
}
|
|
231
239
|
|
|
232
|
-
|
|
240
|
+
const reduced = scalar % BANDERSNATCH_PARAMS.CURVE_ORDER
|
|
241
|
+
if (reduced === 0n) return Bandersnatch.ZERO
|
|
242
|
+
return point.multiply(reduced)
|
|
233
243
|
}
|
|
234
244
|
|
|
235
245
|
/**
|
|
236
246
|
* Adds two curve points together.
|
|
237
|
-
*
|
|
247
|
+
*
|
|
238
248
|
* Performs point addition on the Bandersnatch curve: `P + Q`.
|
|
239
249
|
* This operation is commutative: `add(P, Q) === add(Q, P)`.
|
|
240
|
-
*
|
|
250
|
+
*
|
|
241
251
|
* @param p1 - First curve point
|
|
242
252
|
* @param p2 - Second curve point
|
|
243
253
|
* @returns The sum of the two points: `p1 + p2`
|
|
@@ -249,10 +259,10 @@ export class BandersnatchCurve {
|
|
|
249
259
|
|
|
250
260
|
/**
|
|
251
261
|
* Doubles a curve point.
|
|
252
|
-
*
|
|
262
|
+
*
|
|
253
263
|
* Computes `2 * point` on the Bandersnatch curve. This is equivalent to
|
|
254
264
|
* `add(point, point)` but is typically more efficient.
|
|
255
|
-
*
|
|
265
|
+
*
|
|
256
266
|
* @param point - The curve point to double
|
|
257
267
|
* @returns The doubled point: `2 * point`
|
|
258
268
|
* @throws {Error} If the point is invalid or not on the curve
|
|
@@ -263,10 +273,10 @@ export class BandersnatchCurve {
|
|
|
263
273
|
|
|
264
274
|
/**
|
|
265
275
|
* Negates a curve point.
|
|
266
|
-
*
|
|
276
|
+
*
|
|
267
277
|
* Computes the additive inverse of a point on the Bandersnatch curve.
|
|
268
278
|
* The result satisfies: `add(point, negate(point)) === INFINITY`.
|
|
269
|
-
*
|
|
279
|
+
*
|
|
270
280
|
* @param point - The curve point to negate
|
|
271
281
|
* @returns The negated point: `-point`
|
|
272
282
|
* @throws {Error} If the point is invalid or not on the curve
|
|
@@ -277,10 +287,10 @@ export class BandersnatchCurve {
|
|
|
277
287
|
|
|
278
288
|
/**
|
|
279
289
|
* Checks if a point lies on the Bandersnatch curve.
|
|
280
|
-
*
|
|
290
|
+
*
|
|
281
291
|
* Validates that the point satisfies the Twisted Edwards curve equation:
|
|
282
292
|
* `a*x^2 + y^2 = 1 + d*x^2*y^2` where `a = -5` and `d` is the curve parameter.
|
|
283
|
-
*
|
|
293
|
+
*
|
|
284
294
|
* @param point - The curve point to validate
|
|
285
295
|
* @returns `true` if the point is on the curve, `false` otherwise
|
|
286
296
|
*/
|
|
@@ -290,10 +300,10 @@ export class BandersnatchCurve {
|
|
|
290
300
|
|
|
291
301
|
/**
|
|
292
302
|
* Gets the generator point (base point) of the Bandersnatch curve.
|
|
293
|
-
*
|
|
303
|
+
*
|
|
294
304
|
* The generator is a point on the curve that generates the prime subgroup G.
|
|
295
305
|
* All points in the prime subgroup can be expressed as scalar multiples of the generator.
|
|
296
|
-
*
|
|
306
|
+
*
|
|
297
307
|
* @returns The generator point G
|
|
298
308
|
*/
|
|
299
309
|
static get GENERATOR() {
|
|
@@ -302,10 +312,10 @@ export class BandersnatchCurve {
|
|
|
302
312
|
|
|
303
313
|
/**
|
|
304
314
|
* Gets the identity point (point at infinity) of the Bandersnatch curve.
|
|
305
|
-
*
|
|
315
|
+
*
|
|
306
316
|
* The identity point is the neutral element for point addition:
|
|
307
317
|
* `add(point, INFINITY) === point` for any point on the curve.
|
|
308
|
-
*
|
|
318
|
+
*
|
|
309
319
|
* @returns The identity point (point at infinity)
|
|
310
320
|
*/
|
|
311
321
|
static get INFINITY() {
|
|
@@ -314,11 +324,11 @@ export class BandersnatchCurve {
|
|
|
314
324
|
|
|
315
325
|
/**
|
|
316
326
|
* Converts a curve point to its byte representation.
|
|
317
|
-
*
|
|
327
|
+
*
|
|
318
328
|
* Serializes the point to bytes, typically used for challenge generation
|
|
319
329
|
* in cryptographic protocols. The output format matches the point compression
|
|
320
330
|
* format used by the curve implementation.
|
|
321
|
-
*
|
|
331
|
+
*
|
|
322
332
|
* @param point - The curve point to hash
|
|
323
333
|
* @returns Byte representation of the point
|
|
324
334
|
* @throws {Error} If the point is invalid
|
|
@@ -329,10 +339,10 @@ export class BandersnatchCurve {
|
|
|
329
339
|
|
|
330
340
|
/**
|
|
331
341
|
* Gets the order (cardinality) of the prime subgroup of the Bandersnatch curve.
|
|
332
|
-
*
|
|
342
|
+
*
|
|
333
343
|
* The curve order is the number of points in the prime subgroup G.
|
|
334
344
|
* For any point P in G, `scalarMultiply(P, CURVE_ORDER) === INFINITY`.
|
|
335
|
-
*
|
|
345
|
+
*
|
|
336
346
|
* @returns The curve order as a bigint
|
|
337
347
|
*/
|
|
338
348
|
static get CURVE_ORDER() {
|
package/src/glv.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GLV (Gallant-Lambert-Vanstone) endomorphism for Bandersnatch scalar multiplication.
|
|
3
|
+
*
|
|
4
|
+
* Decomposes a ~253-bit scalar multiplication [k]P into two ~127-bit multiplications
|
|
5
|
+
* using an efficiently computable endomorphism phi:
|
|
6
|
+
* [k]P = [k1]P + [k2]phi(P) where k = k1 + lambda*k2, |k1|,|k2| ~ sqrt(r)
|
|
7
|
+
*
|
|
8
|
+
* Combined with Shamir's trick (simultaneous double-and-add), this halves the
|
|
9
|
+
* number of doublings from ~253 to ~127, yielding ~40% speedup.
|
|
10
|
+
*
|
|
11
|
+
* Reference: Bandersnatch paper (https://eprint.iacr.org/2021/1152)
|
|
12
|
+
* Constants derived from: ZKNoxHQ/PyBandersnatch sage scripts
|
|
13
|
+
* Algorithm from: arkworks-algebra/ec/src/scalar_mul/glv.rs
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { EdwardsPoint } from '@noble/curves/abstract/edwards.js'
|
|
17
|
+
import { BANDERSNATCH_PARAMS } from './config.ts'
|
|
18
|
+
import { Bandersnatch } from './curve.ts'
|
|
19
|
+
import { mod, modInverse } from './math.ts'
|
|
20
|
+
|
|
21
|
+
const P = BANDERSNATCH_PARAMS.FIELD_MODULUS
|
|
22
|
+
const R = BANDERSNATCH_PARAMS.CURVE_ORDER
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Endomorphism rational map coefficients for the Twisted Edwards form.
|
|
26
|
+
*
|
|
27
|
+
* phi(x, y) in projective TE coords (z=1 for affine input):
|
|
28
|
+
* x_phi = x * (AY4*y^4 + AY2Z2*y^2 + AZ4) / (y * (CY2*y^2 + CZ2))
|
|
29
|
+
* y_phi = (BY2*y^2 + BZ2) / (CY2*y^2 + CZ2)
|
|
30
|
+
*
|
|
31
|
+
* Derived via sage from the degree-2 isogeny on the Weierstrass form,
|
|
32
|
+
* transferred to TE coordinates.
|
|
33
|
+
*/
|
|
34
|
+
const AY4 = 0x1d46e71b2d28e06c42bc1f5a41f4a0156d070863689e8862eb12927f72f308c3n
|
|
35
|
+
const AY2Z2 =
|
|
36
|
+
0x20b21e58881722d68c92fa09709ea65d716e869843e94c821df033483694a51an
|
|
37
|
+
const AZ4 = 0x1373fe65dcb354e5209f902de5b37008d6c2721d8d6d5fb556e8b7e969c053c9n
|
|
38
|
+
const BY2 = 0x33937d60e9a0dd55ed1f9030e7c8b6fa9c42e1e41d2f1361a0fed9630f711caen
|
|
39
|
+
const BZ2 = 0x39a33e54438fe0155ae18e93205d4395acfe4be1127ca6458fc0270450b1b50dn
|
|
40
|
+
const CY2 = 0x2cdc91c2ed341d7901e6d6ece64cd98591c66ba64cdc7109d1bdd9cb6f93ee68n
|
|
41
|
+
const CZ2 = 0x405a29f23ffc9ff2461a47d721d9210ab77ac21ee2cf489d5f01269bf08ee353n
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Scalar decomposition lattice basis (LLL-reduced).
|
|
45
|
+
* Used to decompose k into k1 + lambda*k2 with |k1|, |k2| ~ sqrt(r).
|
|
46
|
+
*/
|
|
47
|
+
const M1 = -113482231691339203864511368254957623327n
|
|
48
|
+
const M2 = 10741319382058138887739339959866629956n
|
|
49
|
+
const M3 = 21482638764116277775478679919733259912n
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Floor division for BigInt (towards negative infinity).
|
|
53
|
+
* JS BigInt `/` truncates towards zero; this corrects for negative dividends.
|
|
54
|
+
*/
|
|
55
|
+
function floorDiv(a: bigint, b: bigint): bigint {
|
|
56
|
+
const q = a / b
|
|
57
|
+
const r = a % b
|
|
58
|
+
if (r !== 0n && r < 0n !== b < 0n) {
|
|
59
|
+
return q - 1n
|
|
60
|
+
}
|
|
61
|
+
return q
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function abs(x: bigint): bigint {
|
|
65
|
+
return x < 0n ? -x : x
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function bitLength(x: bigint): number {
|
|
69
|
+
if (x === 0n) return 0
|
|
70
|
+
let v = x < 0n ? -x : x
|
|
71
|
+
let bits = 0
|
|
72
|
+
while (v > 0n) {
|
|
73
|
+
bits++
|
|
74
|
+
v >>= 1n
|
|
75
|
+
}
|
|
76
|
+
return bits
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Compute the Bandersnatch endomorphism phi(P) on a Twisted Edwards point.
|
|
81
|
+
*
|
|
82
|
+
* phi is the sqrt(-2) endomorphism satisfying phi(P) = [lambda]P for all P in
|
|
83
|
+
* the prime-order subgroup, where lambda is the endomorphism eigenvalue.
|
|
84
|
+
*
|
|
85
|
+
* The rational map operates on affine TE coordinates:
|
|
86
|
+
* x_phi = x * (AY4*y^4 + AY2Z2*y^2 + AZ4) / (y * (CY2*y^2 + CZ2))
|
|
87
|
+
* y_phi = (BY2*y^2 + BZ2) / (CY2*y^2 + CZ2)
|
|
88
|
+
*
|
|
89
|
+
* Cost: ~12 field multiplications + 1 field inversion (constant, independent of scalar size).
|
|
90
|
+
*/
|
|
91
|
+
export function endomorphism(point: EdwardsPoint): EdwardsPoint {
|
|
92
|
+
if (point.equals(Bandersnatch.ZERO)) {
|
|
93
|
+
return Bandersnatch.ZERO
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const { x, y } = point.toAffine()
|
|
97
|
+
|
|
98
|
+
const y2 = mod(y * y, P)
|
|
99
|
+
const y4 = mod(y2 * y2, P)
|
|
100
|
+
|
|
101
|
+
const xNumerator = mod(x * mod(AY4 * y4 + AY2Z2 * y2 + AZ4, P), P)
|
|
102
|
+
|
|
103
|
+
const denominator = mod(CY2 * y2 + CZ2, P)
|
|
104
|
+
const denominatorInv = modInverse(denominator, P)
|
|
105
|
+
|
|
106
|
+
const yDenominator = mod(y * denominator, P)
|
|
107
|
+
const yDenominatorInv = modInverse(yDenominator, P)
|
|
108
|
+
|
|
109
|
+
const xPhi = mod(xNumerator * yDenominatorInv, P)
|
|
110
|
+
const yPhi = mod(mod(BY2 * y2 + BZ2, P) * denominatorInv, P)
|
|
111
|
+
|
|
112
|
+
return Bandersnatch.fromAffine({ x: xPhi, y: yPhi })
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Decompose scalar k into (k1, k2) such that k === k1 + lambda * k2 (mod r),
|
|
117
|
+
* with |k1|, |k2| approximately sqrt(r) (~127 bits).
|
|
118
|
+
*
|
|
119
|
+
* Uses Babai's nearest-plane algorithm with the LLL-reduced lattice basis.
|
|
120
|
+
*
|
|
121
|
+
* @returns [sign1, abs_k1, sign2, abs_k2] where sign is true for positive
|
|
122
|
+
*/
|
|
123
|
+
export function scalarDecomposition(
|
|
124
|
+
k: bigint,
|
|
125
|
+
): [boolean, bigint, boolean, bigint] {
|
|
126
|
+
const b0 = floorDiv(k * M1, R)
|
|
127
|
+
const b1 = floorDiv(k * M2, R)
|
|
128
|
+
|
|
129
|
+
const s0 = k - b0 * M1 - b1 * M3
|
|
130
|
+
const s1 = -(b0 * M2 + b1 * -M1)
|
|
131
|
+
|
|
132
|
+
return [s0 >= 0n, abs(s0), s1 >= 0n, abs(s1)]
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* GLV-accelerated scalar multiplication using Shamir's trick.
|
|
137
|
+
*
|
|
138
|
+
* Computes [scalar]P by:
|
|
139
|
+
* 1. Decomposing scalar into two half-size scalars (k1, k2)
|
|
140
|
+
* 2. Computing phi(P) via the endomorphism
|
|
141
|
+
* 3. Running a single simultaneous double-and-add loop over both scalars
|
|
142
|
+
*
|
|
143
|
+
* The loop iterates ~127 bits instead of ~253, roughly halving the doublings.
|
|
144
|
+
*
|
|
145
|
+
* @param point - The curve point P (must be in the prime-order subgroup)
|
|
146
|
+
* @param scalar - The scalar k (must be in range [1, r-1] after reduction)
|
|
147
|
+
*/
|
|
148
|
+
export function glvMultiply(point: EdwardsPoint, scalar: bigint): EdwardsPoint {
|
|
149
|
+
if (scalar === 0n) {
|
|
150
|
+
return Bandersnatch.ZERO
|
|
151
|
+
}
|
|
152
|
+
if (point.equals(Bandersnatch.ZERO)) {
|
|
153
|
+
return Bandersnatch.ZERO
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const reduced = ((scalar % R) + R) % R
|
|
157
|
+
if (reduced === 0n) {
|
|
158
|
+
return Bandersnatch.ZERO
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const [sign1, absK1, sign2, absK2] = scalarDecomposition(reduced)
|
|
162
|
+
|
|
163
|
+
if (absK1 === 0n && absK2 === 0n) {
|
|
164
|
+
return Bandersnatch.ZERO
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const b1 = sign1 ? point : point.negate()
|
|
168
|
+
const b2 = sign2 ? endomorphism(point) : endomorphism(point).negate()
|
|
169
|
+
|
|
170
|
+
if (absK1 === 0n) {
|
|
171
|
+
return absK2 === 1n ? b2 : b2.multiply(absK2)
|
|
172
|
+
}
|
|
173
|
+
if (absK2 === 0n) {
|
|
174
|
+
return absK1 === 1n ? b1 : b1.multiply(absK1)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const b1b2 = b1.add(b2)
|
|
178
|
+
|
|
179
|
+
const len1 = bitLength(absK1)
|
|
180
|
+
const len2 = bitLength(absK2)
|
|
181
|
+
const maxLen = len1 > len2 ? len1 : len2
|
|
182
|
+
|
|
183
|
+
let res = Bandersnatch.ZERO
|
|
184
|
+
let started = false
|
|
185
|
+
|
|
186
|
+
for (let i = maxLen - 1; i >= 0; i--) {
|
|
187
|
+
if (started) {
|
|
188
|
+
res = res.double()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const bit1 = (absK1 >> BigInt(i)) & 1n
|
|
192
|
+
const bit2 = (absK2 >> BigInt(i)) & 1n
|
|
193
|
+
|
|
194
|
+
if (bit1 === 1n && bit2 === 1n) {
|
|
195
|
+
res = started ? res.add(b1b2) : b1b2
|
|
196
|
+
started = true
|
|
197
|
+
} else if (bit1 === 1n) {
|
|
198
|
+
res = started ? res.add(b1) : b1
|
|
199
|
+
started = true
|
|
200
|
+
} else if (bit2 === 1n) {
|
|
201
|
+
res = started ? res.add(b2) : b2
|
|
202
|
+
started = true
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return res
|
|
207
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
* with all necessary operations for cryptographic applications.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
// Export types
|
|
9
|
-
export type { CurvePoint } from './types'
|
|
10
8
|
// Export curve parameters
|
|
11
|
-
export { BANDERSNATCH_PARAMS } from './config'
|
|
12
|
-
// Elligator2 hash-to-curve is now available in bandersnatch-vrf package
|
|
9
|
+
export { BANDERSNATCH_PARAMS } from './config.ts'
|
|
13
10
|
// Export curve implementations
|
|
14
|
-
// Temporary alias for legacy compatibility
|
|
15
11
|
export {
|
|
16
|
-
BandersnatchCurve,
|
|
17
12
|
Bandersnatch,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
BandersnatchCurve,
|
|
14
|
+
} from './curve.ts'
|
|
15
|
+
// Export GLV endomorphism
|
|
16
|
+
export { endomorphism, glvMultiply, scalarDecomposition } from './glv.ts'
|
|
17
|
+
// Export math utilities
|
|
18
|
+
export * from './math.ts'
|
|
19
|
+
// Export types
|
|
20
|
+
export type { CurvePoint } from './types.ts'
|
package/src/math.ts
CHANGED
|
@@ -10,10 +10,10 @@ import {
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Computes the non-negative modular reduction.
|
|
13
|
-
*
|
|
13
|
+
*
|
|
14
14
|
* Returns `a mod m` where the result is always non-negative (0 <= result < m).
|
|
15
15
|
* This handles negative inputs correctly by adding the modulus to negative results.
|
|
16
|
-
*
|
|
16
|
+
*
|
|
17
17
|
* @param a - The value to reduce
|
|
18
18
|
* @param m - The modulus
|
|
19
19
|
* @returns Non-negative result of `a mod m`
|
|
@@ -25,10 +25,10 @@ export function mod(a: bigint, m: bigint): bigint {
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Computes the modular inverse using the extended Euclidean algorithm.
|
|
28
|
-
*
|
|
28
|
+
*
|
|
29
29
|
* Finds the value `x` such that `(a * x) mod m = 1`. The modular inverse exists
|
|
30
30
|
* if and only if `gcd(a, m) = 1`.
|
|
31
|
-
*
|
|
31
|
+
*
|
|
32
32
|
* @param a - The value to find the inverse of
|
|
33
33
|
* @param m - The modulus
|
|
34
34
|
* @returns The modular inverse of `a` modulo `m`
|
|
@@ -53,10 +53,10 @@ export function modInverse(a: bigint, m: bigint): bigint {
|
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* Computes the modular square root using the noble package FpSqrt.
|
|
56
|
-
*
|
|
56
|
+
*
|
|
57
57
|
* Finds a value `x` such that `x^2 mod p = value`. The square root exists
|
|
58
58
|
* if and only if `value` is a quadratic residue modulo `p`.
|
|
59
|
-
*
|
|
59
|
+
*
|
|
60
60
|
* @param value - The value to find the square root of
|
|
61
61
|
* @param p - The prime modulus
|
|
62
62
|
* @param field - The field implementation from @noble/curves
|
|
@@ -81,11 +81,10 @@ export function modSqrt(
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
export function numberToBytesLittleEndian(value: bigint): Uint8Array {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
return bytes
|
|
84
|
+
const bytes = new Uint8Array(32)
|
|
85
|
+
const hex = value.toString(16).padStart(64, '0')
|
|
86
|
+
for (let i = 0; i < 32; i++) {
|
|
87
|
+
bytes[i] = Number.parseInt(hex.slice(62 - i * 2, 64 - i * 2), 16)
|
|
90
88
|
}
|
|
91
|
-
|
|
89
|
+
return bytes
|
|
90
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Represents a point on an elliptic curve.
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* This interface defines the structure for curve points used in Bandersnatch
|
|
9
9
|
* curve operations. Points can be either finite points with (x, y) coordinates
|
|
10
10
|
* or the point at infinity.
|
|
11
|
-
*
|
|
11
|
+
*
|
|
12
12
|
* @interface CurvePoint
|
|
13
13
|
*/
|
|
14
14
|
export interface CurvePoint {
|
|
@@ -19,4 +19,3 @@ export interface CurvePoint {
|
|
|
19
19
|
/** Whether this point is the point at infinity (identity element) */
|
|
20
20
|
isInfinity: boolean
|
|
21
21
|
}
|
|
22
|
-
|