bsv-sdk 0.14.0 → 0.16.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -0
  3. data/README.md +14 -2
  4. data/lib/bsv/auth/auth_middleware.rb +6 -6
  5. data/lib/bsv/auth/certificate.rb +16 -16
  6. data/lib/bsv/auth/master_certificate.rb +5 -5
  7. data/lib/bsv/auth/nonce.rb +13 -13
  8. data/lib/bsv/auth/peer.rb +53 -53
  9. data/lib/bsv/auth/verifiable_certificate.rb +1 -1
  10. data/lib/bsv/identity/client.rb +26 -32
  11. data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +17 -11
  12. data/lib/bsv/mcp/tools/check_balance.rb +16 -4
  13. data/lib/bsv/mcp/tools/fetch_tx.rb +11 -4
  14. data/lib/bsv/mcp/tools/fetch_utxos.rb +16 -4
  15. data/lib/bsv/network/arc.rb +13 -153
  16. data/lib/bsv/network/whats_on_chain.rb +13 -107
  17. data/lib/bsv/overlay/admin_token_template.rb +4 -4
  18. data/lib/bsv/primitives/base58.rb +2 -1
  19. data/lib/bsv/primitives/curve.rb +37 -12
  20. data/lib/bsv/primitives/ecdsa.rb +4 -4
  21. data/lib/bsv/primitives/openssl_ec_shim.rb +32 -5
  22. data/lib/bsv/primitives/private_key.rb +2 -2
  23. data/lib/bsv/primitives/public_key.rb +1 -1
  24. data/lib/bsv/primitives/schnorr.rb +4 -4
  25. data/lib/bsv/primitives/secp256k1.rb +4 -595
  26. data/lib/bsv/primitives/signature.rb +2 -0
  27. data/lib/bsv/primitives/signed_message.rb +6 -5
  28. data/lib/bsv/registry/client.rb +23 -27
  29. data/lib/bsv/script/push_drop_template.rb +4 -4
  30. data/lib/bsv/secp256k1_native.bundle +0 -0
  31. data/lib/bsv/version.rb +1 -1
  32. data/lib/bsv/wallet/errors.rb +47 -0
  33. data/lib/bsv/wallet/interface/brc100.rb +267 -0
  34. data/lib/bsv/wallet/interface.rb +9 -0
  35. data/lib/bsv/wallet/proto_wallet/key_deriver.rb +150 -0
  36. data/lib/bsv/wallet/proto_wallet/validators.rb +74 -0
  37. data/lib/bsv/wallet/proto_wallet.rb +321 -0
  38. data/lib/bsv/wallet.rb +16 -0
  39. data/lib/bsv-sdk.rb +4 -1
  40. metadata +37 -1
@@ -1,602 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Single-letter parameter names (k, p, q, x, y, z, etc.) match standard
4
- # elliptic-curve mathematical notation and the BSV TypeScript reference SDK
5
- # this module is ported from. The whole-module length cop is disabled because
6
- # the curve implementation (field arithmetic + Jacobian point ops + wNAF
7
- # scalar multiplication + Point class) intentionally lives in one module to
8
- # keep the secp256k1 surface coherent.
9
- # rubocop:disable Naming/MethodParameterName, Metrics/ModuleLength
3
+ require 'secp256k1'
10
4
 
11
5
  module BSV
12
6
  module Primitives
13
- # Pure Ruby secp256k1 elliptic curve implementation.
14
- #
15
- # Provides field arithmetic, point operations with Jacobian coordinates,
16
- # and windowed-NAF scalar multiplication. Ported from the BSV TypeScript
17
- # SDK reference implementation.
18
- #
19
- # All field operations work on plain Ruby +Integer+ values (arbitrary
20
- # precision, C-backed in MRI). No external gems required.
21
- module Secp256k1
22
- # The secp256k1 field prime: p = 2^256 - 2^32 - 977
23
- P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
24
-
25
- # The curve order (number of points on the curve).
26
- N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
27
-
28
- # Half the curve order, used for low-S normalisation (BIP-62).
29
- HALF_N = N >> 1
30
-
31
- # Generator point x-coordinate.
32
- GX = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
33
-
34
- # Generator point y-coordinate.
35
- GY = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
36
-
37
- # (P + 1) / 4 — used for modular square root since P ≡ 3 (mod 4).
38
- P_PLUS1_DIV4 = (P + 1) >> 2
39
-
40
- # 256-bit mask for fast reduction.
41
- MASK_256 = (1 << 256) - 1
42
-
43
- module_function
44
-
45
- # -------------------------------------------------------------------
46
- # Byte conversion helpers
47
- # -------------------------------------------------------------------
48
-
49
- # Convert a big-endian binary string to an Integer.
50
- #
51
- # @param bytes [String] binary string (ASCII-8BIT)
52
- # @return [Integer]
53
- def bytes_to_int(bytes)
54
- bytes.unpack1('H*').to_i(16)
55
- end
56
-
57
- # Convert an Integer to a fixed-length big-endian binary string.
58
- #
59
- # @param n [Integer] the integer to convert
60
- # @param length [Integer] desired byte length (default 32)
61
- # @return [String] binary string (ASCII-8BIT)
62
- def int_to_bytes(n, length = 32)
63
- raise ArgumentError, 'negative integer' if n.negative?
64
-
65
- hex = n.to_s(16)
66
- hex = "0#{hex}" if hex.length.odd?
67
- raise ArgumentError, "integer too large for #{length} bytes" if hex.length > length * 2
68
-
69
- hex = hex.rjust(length * 2, '0')
70
- [hex].pack('H*')
71
- end
72
-
73
- # -------------------------------------------------------------------
74
- # Field arithmetic (mod P)
75
- # -------------------------------------------------------------------
76
-
77
- # Fast reduction modulo the secp256k1 prime.
78
- #
79
- # Exploits the structure P = 2^256 - 2^32 - 977 to avoid generic
80
- # modular division. Two folding passes plus a conditional subtraction.
81
- #
82
- # @param x [Integer] non-negative integer
83
- # @return [Integer] x mod P, in range [0, P)
84
- def fred(x)
85
- # First fold
86
- hi = x >> 256
87
- x = (x & MASK_256) + (hi << 32) + (hi * 977)
88
-
89
- # Second fold (hi <= 2^32 + 977, so one more pass suffices)
90
- hi = x >> 256
91
- x = (x & MASK_256) + (hi << 32) + (hi * 977)
92
-
93
- # Final conditional subtraction
94
- x >= P ? x - P : x
95
- end
96
-
97
- # Modular multiplication in the field.
98
- def fmul(a, b)
99
- fred(a * b)
100
- end
101
-
102
- # Modular squaring in the field.
103
- def fsqr(a)
104
- fred(a * a)
105
- end
106
-
107
- # Modular addition in the field.
108
- def fadd(a, b)
109
- fred(a + b)
110
- end
111
-
112
- # Modular subtraction in the field.
113
- def fsub(a, b)
114
- a >= b ? a - b : P - (b - a)
115
- end
116
-
117
- # Modular negation in the field.
118
- def fneg(a)
119
- a.zero? ? 0 : P - a
120
- end
121
-
122
- # Modular multiplicative inverse in the field (Fermat's little theorem).
123
- #
124
- # @param a [Integer] value to invert (must be non-zero mod P)
125
- # @return [Integer] a^(P-2) mod P
126
- # @raise [ArgumentError] if a is zero mod P
127
- def finv(a)
128
- raise ArgumentError, 'field inverse is undefined for zero' if (a % P).zero?
129
-
130
- a.pow(P - 2, P)
131
- end
132
-
133
- # Modular square root in the field.
134
- #
135
- # Uses the identity sqrt(a) = a^((P+1)/4) mod P, valid because
136
- # P ≡ 3 (mod 4). Returns +nil+ if +a+ is not a quadratic residue.
137
- #
138
- # @param a [Integer]
139
- # @return [Integer, nil] the square root, or nil if none exists
140
- def fsqrt(a)
141
- r = a.pow(P_PLUS1_DIV4, P)
142
- fsqr(r) == (a % P) ? r : nil
143
- end
144
-
145
- # -------------------------------------------------------------------
146
- # Scalar arithmetic (mod N)
147
- # -------------------------------------------------------------------
148
-
149
- # Reduce modulo the curve order.
150
- def scalar_mod(a)
151
- r = a % N
152
- r += N if r.negative?
153
- r
154
- end
155
-
156
- # Scalar multiplicative inverse (Fermat).
157
- #
158
- # @raise [ArgumentError] if a is zero mod N
159
- def scalar_inv(a)
160
- raise ArgumentError, 'scalar inverse is undefined for zero' if (a % N).zero?
161
-
162
- a.pow(N - 2, N)
163
- end
164
-
165
- # Scalar multiplication mod N.
166
- def scalar_mul(a, b)
167
- (a * b) % N
168
- end
169
-
170
- # Scalar addition mod N.
171
- def scalar_add(a, b)
172
- (a + b) % N
173
- end
174
-
175
- # -------------------------------------------------------------------
176
- # Jacobian point operations (internal)
177
- #
178
- # Points are represented as [X, Y, Z] arrays of Integers.
179
- # The point at infinity is [0, 1, 0].
180
- # -------------------------------------------------------------------
181
-
182
- # @!visibility private
183
- JP_INFINITY = [0, 1, 0].freeze
184
-
185
- # Double a Jacobian point.
186
- #
187
- # Formula from hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html
188
- # (a=0 for secp256k1).
189
- #
190
- # @param p [Array(Integer, Integer, Integer)] Jacobian point [X, Y, Z]
191
- # @return [Array(Integer, Integer, Integer)]
192
- def jp_double(p)
193
- x1, y1, z1 = p
194
- return JP_INFINITY if y1.zero?
195
-
196
- y1sq = fsqr(y1)
197
- s = fmul(4, fmul(x1, y1sq))
198
- m = fmul(3, fsqr(x1)) # a=0 for secp256k1
199
- x3 = fsub(fsqr(m), fmul(2, s))
200
- y3 = fsub(fmul(m, fsub(s, x3)), fmul(8, fsqr(y1sq)))
201
- z3 = fmul(2, fmul(y1, z1))
202
- [x3, y3, z3]
203
- end
204
-
205
- # Add two Jacobian points.
206
- #
207
- # @param p [Array] first Jacobian point
208
- # @param q [Array] second Jacobian point
209
- # @return [Array] resulting Jacobian point
210
- def jp_add(p, q)
211
- _px, _py, pz = p
212
- _qx, _qy, qz = q
213
- return q if pz.zero?
214
- return p if qz.zero?
215
-
216
- z1z1 = fsqr(pz)
217
- z2z2 = fsqr(qz)
218
- u1 = fmul(p[0], z2z2)
219
- u2 = fmul(q[0], z1z1)
220
- s1 = fmul(p[1], fmul(z2z2, qz))
221
- s2 = fmul(q[1], fmul(z1z1, pz))
222
-
223
- h = fsub(u2, u1)
224
- r = fsub(s2, s1)
225
-
226
- if h.zero?
227
- return r.zero? ? jp_double(p) : JP_INFINITY
228
- end
229
-
230
- hh = fsqr(h)
231
- hhh = fmul(h, hh)
232
- v = fmul(u1, hh)
233
-
234
- x3 = fsub(fsub(fsqr(r), hhh), fmul(2, v))
235
- y3 = fsub(fmul(r, fsub(v, x3)), fmul(s1, hhh))
236
- z3 = fmul(h, fmul(pz, qz))
237
- [x3, y3, z3]
238
- end
239
-
240
- # Convert a Jacobian point to affine coordinates.
241
- #
242
- # @param jp [Array(Integer, Integer, Integer)]
243
- # @return [Array(Integer, Integer)] affine [x, y], or nil for infinity
244
- def jp_to_affine(jp)
245
- _x, _y, z = jp
246
- return nil if z.zero?
247
-
248
- zinv = finv(z)
249
- zinv2 = fsqr(zinv)
250
- x = fmul(jp[0], zinv2)
251
- y = fmul(jp[1], fmul(zinv2, zinv))
252
- [x, y]
253
- end
254
-
255
- # -------------------------------------------------------------------
256
- # Windowed-NAF scalar multiplication (variable-time, public scalars)
257
- # -------------------------------------------------------------------
258
-
259
- # @!visibility private
260
- # Maximum number of entries kept in the wNAF precomputation cache.
261
- # Bounds memory usage for long-running processes (e.g. servers).
262
- WNAF_CACHE_MAX = 512
263
-
264
- # @!visibility private
265
- # Cache for precomputed wNAF tables, keyed by "window:x:y".
266
- # Evicts oldest entry when the LRU limit is reached.
267
- WNAF_TABLE_CACHE = {} # rubocop:disable Style/MutableConstant
268
-
269
- # @!visibility private
270
- # Multiply a point by a scalar using windowed-NAF.
271
- #
272
- # Variable-time algorithm — suitable only for public scalars (e.g.
273
- # signature verification). Secret-scalar paths MUST use
274
- # {scalar_multiply_ct} instead.
275
- #
276
- # Internal method — use {Point#mul} or {Point#mul_ct} instead.
277
- # Exposed as a module function only so the nested Point class can
278
- # call it; not part of the public API.
279
- #
280
- # @param k [Integer] the scalar (must be in [1, N))
281
- # @param px [Integer] affine x-coordinate of the base point
282
- # @param py [Integer] affine y-coordinate of the base point
283
- # @param window [Integer] wNAF window size (default 5)
284
- # @return [Array(Integer, Integer, Integer)] result as Jacobian point
285
- def scalar_multiply_wnaf(k, px, py, window = 5)
286
- return JP_INFINITY if k.zero?
287
-
288
- cache_key = "#{window}:#{px.to_s(16)}:#{py.to_s(16)}"
289
- tbl = WNAF_TABLE_CACHE[cache_key]
290
-
291
- if tbl.nil?
292
- # Evict the oldest entry when the cache is full (simple LRU).
293
- WNAF_TABLE_CACHE.delete(WNAF_TABLE_CACHE.keys.first) if WNAF_TABLE_CACHE.size >= WNAF_CACHE_MAX
294
-
295
- tbl_size = 1 << (window - 1) # e.g. w=5 -> 16 entries
296
- tbl = Array.new(tbl_size)
297
- tbl[0] = [px, py, 1]
298
- two_p = jp_double(tbl[0])
299
- 1.upto(tbl_size - 1) do |i|
300
- tbl[i] = jp_add(tbl[i - 1], two_p)
301
- end
302
- WNAF_TABLE_CACHE[cache_key] = tbl
303
- end
304
-
305
- # Build wNAF representation
306
- w_big = 1 << window
307
- w_half = w_big >> 1
308
- wnaf = []
309
- k_tmp = k
310
- while k_tmp.positive?
311
- if k_tmp.odd?
312
- z = k_tmp & (w_big - 1)
313
- z -= w_big if z > w_half
314
- wnaf << z
315
- k_tmp -= z
316
- else
317
- wnaf << 0
318
- end
319
- k_tmp >>= 1
320
- end
321
-
322
- # Accumulate from MSB to LSB
323
- q = JP_INFINITY
324
- (wnaf.length - 1).downto(0) do |i|
325
- q = jp_double(q)
326
- di = wnaf[i]
327
- next if di.zero?
328
-
329
- idx = di.abs >> 1
330
- addend = di.positive? ? tbl[idx] : jp_neg(tbl[idx])
331
- q = jp_add(q, addend)
332
- end
333
- q
334
- end
335
-
336
- # -------------------------------------------------------------------
337
- # Montgomery ladder scalar multiplication (constant-time, secret scalars)
338
- # -------------------------------------------------------------------
339
-
340
- # @!visibility private
341
- # Multiply a point by a scalar using the Montgomery ladder.
342
- #
343
- # Executes a fixed number of iterations (256) with one +jp_double+
344
- # and one +jp_add+ per iteration regardless of the scalar value.
345
- # Use this for ALL secret-scalar paths (key generation, signing,
346
- # ECDH, BIP-32 derivation).
347
- #
348
- # *Best-effort constant-time in interpreted Ruby.* The branch on
349
- # +bit+ selects which register receives each operation, and both
350
- # operations always execute. However, Ruby's interpreter, GC, and
351
- # the early-return branches in +jp_add+/+jp_double+ (for infinity
352
- # edge cases) mean true constant-time execution is not achievable
353
- # without native code. This matches the ts-sdk's TypeScript
354
- # implementation, which has the same structural properties. For
355
- # production deployments requiring side-channel resistance beyond
356
- # what an interpreted language can offer, use a native secp256k1
357
- # library (e.g. libsecp256k1 via FFI).
358
- #
359
- # Internal method — use {Point#mul_ct} instead. Not part of the
360
- # public API.
361
- #
362
- # @param k [Integer] secret scalar (must be in [1, N))
363
- # @param px [Integer] affine x-coordinate of the base point
364
- # @param py [Integer] affine y-coordinate of the base point
365
- # @return [Array(Integer, Integer, Integer)] result as Jacobian point
366
- def scalar_multiply_ct(k, px, py)
367
- return JP_INFINITY if k.zero?
368
-
369
- # r0 accumulates the result; r1 = r0 + base_point at all times.
370
- r0 = JP_INFINITY
371
- r1 = [px, py, 1]
372
-
373
- 256.times do |i|
374
- bit = (k >> (255 - i)) & 1
375
- if bit.zero?
376
- r1 = jp_add(r0, r1)
377
- r0 = jp_double(r0)
378
- else
379
- r0 = jp_add(r0, r1)
380
- r1 = jp_double(r1)
381
- end
382
- end
383
-
384
- r0
385
- end
386
-
387
- # Negate a Jacobian point.
388
- def jp_neg(p)
389
- return p if p[2].zero?
390
-
391
- [p[0], fneg(p[1]), p[2]]
392
- end
393
-
394
- # -------------------------------------------------------------------
395
- # Point class
396
- # -------------------------------------------------------------------
397
-
398
- # An elliptic curve point on secp256k1.
399
- #
400
- # Stores affine coordinates (x, y) or represents the point at infinity.
401
- # Scalar multiplication uses Jacobian coordinates internally with
402
- # windowed-NAF for performance.
403
- class Point
404
- # @return [Integer, nil] x-coordinate (nil for infinity)
405
- attr_reader :x
406
-
407
- # @return [Integer, nil] y-coordinate (nil for infinity)
408
- attr_reader :y
409
-
410
- # @param x [Integer, nil] x-coordinate (nil for infinity)
411
- # @param y [Integer, nil] y-coordinate (nil for infinity)
412
- def initialize(x, y)
413
- @x = x
414
- @y = y
415
- end
416
-
417
- # The point at infinity (additive identity).
418
- #
419
- # @return [Point]
420
- def self.infinity
421
- new(nil, nil)
422
- end
423
-
424
- # The generator point G.
425
- #
426
- # @return [Point]
427
- def self.generator
428
- @generator ||= new(GX, GY)
429
- end
430
-
431
- # Deserialise a point from compressed (33 bytes) or uncompressed
432
- # (65 bytes) SEC1 encoding.
433
- #
434
- # @param bytes [String] binary string
435
- # @return [Point]
436
- # @raise [ArgumentError] if the encoding is invalid or the point
437
- # is not on the curve
438
- def self.from_bytes(bytes)
439
- bytes = bytes.b if bytes.encoding != Encoding::BINARY
440
- prefix = bytes.getbyte(0)
441
-
442
- case prefix
443
- when 0x04 # Uncompressed
444
- raise ArgumentError, 'invalid uncompressed point length' unless bytes.length == 65
445
-
446
- x = Secp256k1.bytes_to_int(bytes[1, 32])
447
- y = Secp256k1.bytes_to_int(bytes[33, 32])
448
- raise ArgumentError, 'x coordinate out of field range' if x >= P
449
- raise ArgumentError, 'y coordinate out of field range' if y >= P
450
-
451
- pt = new(x, y)
452
- raise ArgumentError, 'point is not on the curve' unless pt.on_curve?
453
-
454
- pt
455
- when 0x02, 0x03 # Compressed
456
- raise ArgumentError, 'invalid compressed point length' unless bytes.length == 33
457
-
458
- x = Secp256k1.bytes_to_int(bytes[1, 32])
459
- raise ArgumentError, 'x coordinate out of field range' if x >= P
460
-
461
- y_squared = Secp256k1.fadd(Secp256k1.fmul(Secp256k1.fsqr(x), x), 7)
462
- y = Secp256k1.fsqrt(y_squared)
463
- raise ArgumentError, 'invalid point: x not on curve' if y.nil?
464
-
465
- # Ensure y-parity matches prefix
466
- y = Secp256k1.fneg(y) if (y.odd? ? 0x03 : 0x02) != prefix
467
-
468
- new(x, y)
469
- else
470
- raise ArgumentError, "unknown point prefix: 0x#{prefix.to_s(16).rjust(2, '0')}"
471
- end
472
- end
473
-
474
- # Whether this is the point at infinity.
475
- #
476
- # @return [Boolean]
477
- def infinity?
478
- @x.nil?
479
- end
480
-
481
- # Whether this point lies on the secp256k1 curve (y² = x³ + 7).
482
- #
483
- # @return [Boolean]
484
- def on_curve?
485
- return true if infinity?
486
-
487
- lhs = Secp256k1.fsqr(@y)
488
- rhs = Secp256k1.fadd(Secp256k1.fmul(Secp256k1.fsqr(@x), @x), 7)
489
- lhs == rhs
490
- end
491
-
492
- # Serialise the point in SEC1 format.
493
- #
494
- # @param format [:compressed, :uncompressed]
495
- # @return [String] binary string (33 or 65 bytes)
496
- # @raise [RuntimeError] if the point is at infinity
497
- def to_octet_string(format = :compressed)
498
- raise 'cannot serialise point at infinity' if infinity?
499
-
500
- case format
501
- when :compressed
502
- prefix = @y.odd? ? "\x03".b : "\x02".b
503
- prefix + Secp256k1.int_to_bytes(@x, 32)
504
- when :uncompressed
505
- "\x04".b + Secp256k1.int_to_bytes(@x, 32) + Secp256k1.int_to_bytes(@y, 32)
506
- else
507
- raise ArgumentError, "unknown format: #{format}"
508
- end
509
- end
510
-
511
- # Scalar multiplication: self * scalar (variable-time, wNAF).
512
- #
513
- # Suitable for public scalars only (e.g. signature verification).
514
- # For secret-scalar paths use {#mul_ct}.
515
- #
516
- # @param scalar [Integer] the scalar multiplier
517
- # @return [Point] the resulting point
518
- def mul(scalar)
519
- return self.class.infinity if scalar.zero? || infinity?
520
-
521
- scalar %= N
522
- return self.class.infinity if scalar.zero?
523
-
524
- jp = Secp256k1.scalar_multiply_wnaf(scalar, @x, @y)
525
- affine = Secp256k1.jp_to_affine(jp)
526
- return self.class.infinity if affine.nil?
527
-
528
- self.class.new(affine[0], affine[1])
529
- end
530
-
531
- # Constant-time scalar multiplication: self * scalar (Montgomery ladder).
532
- #
533
- # Processes all 256 bits unconditionally so execution time does not
534
- # depend on the scalar value. Use this for secret-scalar paths:
535
- # key generation, signing, and ECDH shared-secret derivation.
536
- #
537
- # @param scalar [Integer] the secret scalar multiplier
538
- # @return [Point] the resulting point
539
- def mul_ct(scalar)
540
- return self.class.infinity if scalar.zero? || infinity?
541
-
542
- scalar %= N
543
- return self.class.infinity if scalar.zero?
544
-
545
- jp = Secp256k1.scalar_multiply_ct(scalar, @x, @y)
546
- affine = Secp256k1.jp_to_affine(jp)
547
- return self.class.infinity if affine.nil?
548
-
549
- self.class.new(affine[0], affine[1])
550
- end
551
-
552
- # Point addition: self + other.
553
- #
554
- # @param other [Point]
555
- # @return [Point]
556
- def add(other)
557
- return other if infinity?
558
- return self if other.infinity?
559
-
560
- jp1 = [@x, @y, 1]
561
- jp2 = [other.x, other.y, 1]
562
- jp_result = Secp256k1.jp_add(jp1, jp2)
563
- affine = Secp256k1.jp_to_affine(jp_result)
564
- return self.class.infinity if affine.nil?
565
-
566
- self.class.new(affine[0], affine[1])
567
- end
568
-
569
- # Point negation: -self.
570
- #
571
- # @return [Point]
572
- def negate
573
- return self if infinity?
574
-
575
- self.class.new(@x, Secp256k1.fneg(@y))
576
- end
577
-
578
- # Equality comparison.
579
- #
580
- # @param other [Point]
581
- # @return [Boolean]
582
- def ==(other)
583
- return false unless other.is_a?(Point)
584
-
585
- if infinity? && other.infinity?
586
- true
587
- elsif infinity? || other.infinity?
588
- false
589
- else
590
- @x == other.x && @y == other.y
591
- end
592
- end
593
- alias eql? ==
594
-
595
- def hash
596
- infinity? ? 0 : [@x, @y].hash
597
- end
598
- end
599
- end
7
+ # Backwards-compatibility alias: BSV::Primitives::Secp256k1 ::Secp256k1
8
+ # This mapping will be removed in the next major version.
9
+ Secp256k1 = ::Secp256k1
600
10
  end
601
11
  end
602
- # rubocop:enable Naming/MethodParameterName, Metrics/ModuleLength
@@ -59,7 +59,9 @@ module BSV
59
59
 
60
60
  # Parse S
61
61
  s_offset = 4 + r_len
62
+ raise ArgumentError, 'truncated: missing S tag' if s_offset >= bytes.length
62
63
  raise ArgumentError, 'invalid integer tag for S' unless bytes[s_offset] == 0x02
64
+ raise ArgumentError, 'truncated: missing S length' if s_offset + 1 >= bytes.length
63
65
 
64
66
  s_len = bytes[s_offset + 1]
65
67
  raise ArgumentError, 'S length overflows' if s_offset + 2 + s_len > bytes.length
@@ -73,17 +73,18 @@ module BSV
73
73
  else
74
74
  # Specific recipient
75
75
  verifier_pub_bytes = sig.byteslice(37, 33)
76
- verifier_pub_hex = verifier_pub_bytes.unpack1('H*')
77
76
 
78
77
  if recipient.nil?
79
78
  raise ArgumentError,
80
- "this signature can only be verified with knowledge of a specific private key. The associated public key is: #{verifier_pub_hex}"
79
+ 'this signature can only be verified with knowledge of a specific private key. ' \
80
+ "The associated public key is: #{verifier_pub_bytes.unpack1('H*')}"
81
81
  end
82
82
 
83
- recipient_pub_hex = recipient.public_key.compressed.unpack1('H*')
84
- if verifier_pub_hex != recipient_pub_hex
83
+ recipient_pub_bytes = recipient.public_key.compressed
84
+ if verifier_pub_bytes != recipient_pub_bytes
85
85
  raise ArgumentError,
86
- "the recipient public key is #{recipient_pub_hex} but the signature requires the recipient to have public key #{verifier_pub_hex}"
86
+ "the recipient public key is #{recipient_pub_bytes.unpack1('H*')} " \
87
+ "but the signature requires the recipient to have public key #{verifier_pub_bytes.unpack1('H*')}"
87
88
  end
88
89
 
89
90
  key_id_offset = 70