secp256k1-native 0.15.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3721e2220de6d65f6bda10fe26a47bd2eaa248ba5d645caf70a0e40901720268
4
- data.tar.gz: e07a7c79de6e4055581fd4a009dd48c375f7deb722bd2a18f5b99e9c444341ef
3
+ metadata.gz: e99016475f085f293dea746296947233bc59879e7e5b864da191ac9760ec665b
4
+ data.tar.gz: 409aac96468c5557951cb754a8bb0e9fd49e16b7526fdb31b22cb8441b2b2d85
5
5
  SHA512:
6
- metadata.gz: a5b109ef38d0589630ac73eab3ff5b910e2592926fe949aea7c12c4b9285dc94242ebfb449075276666806c659c32e7366cd61e3f43bb3bbd6a12d848ec12330
7
- data.tar.gz: 4298a51739c11a6a5d01231fcf9274f1bfcdd7a93518b2920c255b8361f10bdf25c5e0c85e2c080a11380c55f4382b6980cc7a90d0eaab6f2f06b518c64da91f
6
+ metadata.gz: 8fd3c3f89628724cfbad9866ba636c131350fcbf4977016d63eb4ec0c18568a037be17af63ff792981a49c74915942291384afc25989a097d31f6056f3fe4b0e
7
+ data.tar.gz: ac8290a66020b4ba4d5e0f4f77228bee25bf984eb517997b3178b4b51b165cf88a905c489bd429bc68a214d052bf56f412e27588ab07d434512a582c576fe47b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.16.0] - 2026-04-29
4
+
5
+ ### Breaking Changes
6
+
7
+ - `Point#mul` is now constant-time (Montgomery ladder) by default, matching OpenSSL behaviour. The previous variable-time wNAF implementation is available as `Point#mul_vt`
8
+ - `Point#mul` raises `InsecureOperationError` without the native C extension unless explicitly allowed via `SECP256K1_ALLOW_PURE_RUBY_CT=1` or `Secp256k1.allow_pure_ruby_ct!`
9
+
10
+ ### Added
11
+
12
+ - `Point#mul_vt` for explicit variable-time scalar multiplication (public scalars only)
13
+ - `Secp256k1.native?` to check whether the C extension is loaded
14
+ - `Secp256k1.allow_pure_ruby_ct!` and `SECP256K1_ALLOW_PURE_RUBY_CT` env var for opting in to pure-Ruby constant-time operations
15
+ - Evidence-based risk assessment documentation (`docs/risks.md`)
16
+ - MkDocs site with GitHub Pages automation
17
+ - YARD-generated API reference
18
+
19
+ ### Changed
20
+
21
+ - `Point#mul_ct` is now a deprecated alias for `Point#mul`
22
+ - Licence changed from Open BSV License to MIT
23
+ - Documentation reorganised into focused documents (architecture, security, performance, design rationale)
24
+
3
25
  ## [0.15.0] - 2026-04-27
4
26
 
5
27
  ### Added
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # secp256k1-native
2
2
 
3
+ > **Before using a custom cryptographic implementation, read [Evaluating the risks](https://sgbett.github.io/secp256k1-native/risks/) — it examines what the empirical evidence says about rolling your own crypto and where this gem sits in that landscape.**
4
+
3
5
  Pure native C secp256k1 implementation for Ruby (no libsecp256k1 dependency).
4
6
 
5
- Provides secp256k1 elliptic curve cryptography for Ruby — field arithmetic, scalar operations, Jacobian point arithmetic, and constant-time scalar multiplication — via an optional native C extension. The gem ships a pure-Ruby base layer that works out of the box on any Ruby 2.7+ platform, with the C extension as an optional accelerator (~22× speedup) that is silently skipped when unavailable.
7
+ Provides secp256k1 elliptic curve cryptography for Ruby — field arithmetic, scalar operations, Jacobian point arithmetic, and constant-time scalar multiplication — via an optional native C extension. The gem ships a pure-Ruby base layer that works out of the box on any Ruby 2.7+ platform, with the C extension providing constant-time guarantees and ~22x acceleration when available.
6
8
 
7
9
  Used by the [bsv-ruby-sdk](https://github.com/sgbett/bsv-ruby-sdk) and suitable for any Ruby project requiring secp256k1 operations.
8
10
 
@@ -36,14 +38,14 @@ require 'secp256k1'
36
38
  # Generator point
37
39
  g = Secp256k1::Point.generator
38
40
 
39
- # Scalar multiplication (variable-time, for public scalars)
40
- scalar = 0xdeadbeef
41
- point = g.mul(scalar)
42
- puts point.x.to_s(16)
43
-
44
- # Constant-time scalar multiplication (for secret scalars)
41
+ # Scalar multiplication (constant-time by default — safe for all scalars)
45
42
  secret = 0xcafebabe
46
- pubkey = g.mul_ct(secret)
43
+ pubkey = g.mul(secret)
44
+ puts pubkey.x.to_s(16)
45
+
46
+ # Variable-time scalar multiplication (faster, for public scalars only)
47
+ scalar = 0xdeadbeef
48
+ point = g.mul_vt(scalar)
47
49
 
48
50
  # SEC1 encoding / decoding
49
51
  compressed = pubkey.to_octet_string(:compressed) # 33 bytes
@@ -95,12 +97,12 @@ The wNAF loop and ECDSA/Schnorr logic remain in Ruby, calling native primitives
95
97
 
96
98
  ### Performance
97
99
 
98
- | Mode | Operations/sec (scalar multiplication) |
99
- |---|---|
100
- | Pure Ruby | ~100 |
101
- | Native C extension | ~2,277 |
100
+ | Mode | Sign (ops/sec) | Verify (ops/sec) |
101
+ |------|---------------|-----------------|
102
+ | Pure Ruby | 100 | 97 |
103
+ | C extension | 2,302 | 1,826 |
102
104
 
103
- The extension provides approximately 22× speedup for scalar multiplication — the dominant cost in signing, public key derivation, and Schnorr proof generation.
105
+ The C extension provides ~23× speedup for signing and ~19× for verification but performance is secondary to security. The primary purpose of the C extension is to provide **hardware-level constant-time guarantees** that Ruby's variable-width `Integer` internals cannot offer. Users handling secret key material should evaluate whether the pure-Ruby implementation is appropriate for their threat model. See [docs/performance.md](docs/performance.md) for detailed analysis.
104
106
 
105
107
  ## Building the native extension
106
108
 
@@ -116,7 +118,7 @@ bundle exec rake compile
116
118
 
117
119
  The compiled bundle is placed at `lib/secp256k1_native.bundle` (macOS) or `lib/secp256k1_native.so` (Linux).
118
120
 
119
- `extconf.rb` checks for `__uint128_t` availability at configure time. If the type is absent, a no-op Makefile is generated and the extension is silently skipped. At runtime, `secp256k1.rb` wraps the `require` in a `rescue LoadError` — if the bundle is absent, the pure-Ruby implementation is used without any error.
121
+ `extconf.rb` checks for `__uint128_t` availability at configure time. If the type is absent, a no-op Makefile is generated and the extension is skipped. At runtime, `secp256k1.rb` wraps the `require` in a `rescue LoadError` — if the bundle is absent, the pure-Ruby implementation is used for public-scalar operations. Constant-time operations (`mul_ct`) will raise `InsecureOperationError` unless explicitly allowed via `SECP256K1_ALLOW_PURE_RUBY_CT=1` or `Secp256k1.allow_pure_ruby_ct!`.
120
122
 
121
123
  ## Running tests
122
124
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Secp256k1
4
- VERSION = '0.15.0'
4
+ VERSION = '0.16.0'
5
5
  end
data/lib/secp256k1.rb CHANGED
@@ -19,6 +19,32 @@ require_relative 'secp256k1/version'
19
19
  # All field operations work on plain Ruby +Integer+ values (arbitrary
20
20
  # precision, C-backed in MRI). No external gems required.
21
21
  module Secp256k1
22
+ # Raised when a constant-time operation is attempted without the native
23
+ # C extension loaded. The pure-Ruby implementation cannot guarantee
24
+ # constant-time execution due to interpreter-introduced timing variability.
25
+ class InsecureOperationError < SecurityError; end
26
+
27
+ # Whether the native C extension is loaded and active.
28
+ #
29
+ # @return [Boolean]
30
+ def self.native?
31
+ @native == true
32
+ end
33
+
34
+ # Explicitly allow constant-time operations in pure-Ruby mode.
35
+ # Call this only after evaluating the risks documented in docs/risks.md.
36
+ def self.allow_pure_ruby_ct!
37
+ @allow_pure_ruby_ct = true
38
+ end
39
+
40
+ # @api private
41
+ def self.pure_ruby_ct_allowed?
42
+ @allow_pure_ruby_ct || ENV.key?('SECP256K1_ALLOW_PURE_RUBY_CT')
43
+ end
44
+
45
+ @native = false
46
+ @allow_pure_ruby_ct = false
47
+
22
48
  # The secp256k1 field prime: p = 2^256 - 2^32 - 977
23
49
  P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
24
50
 
@@ -510,41 +536,58 @@ module Secp256k1
510
536
  end
511
537
  end
512
538
 
513
- # Scalar multiplication: self * scalar (variable-time, wNAF).
539
+ # Scalar multiplication: self * scalar (constant-time, Montgomery ladder).
540
+ #
541
+ # Processes all 256 bits unconditionally so execution time does not
542
+ # depend on the scalar value. Safe for both secret and public scalars.
543
+ # This is the default because the safe path should be the easy path.
514
544
  #
515
- # Suitable for public scalars only (e.g. signature verification).
516
- # For secret-scalar paths use {#mul_ct}.
545
+ # For performance-critical public-scalar paths (e.g. batch verification)
546
+ # where constant-time is unnecessary, use {#mul_vt}.
547
+ #
548
+ # Raises {InsecureOperationError} if the native C extension is not loaded,
549
+ # unless explicitly allowed via {Secp256k1.allow_pure_ruby_ct!} or the
550
+ # +SECP256K1_ALLOW_PURE_RUBY_CT+ environment variable.
517
551
  #
518
552
  # @param scalar [Integer] the scalar multiplier
519
553
  # @return [Point] the resulting point
520
554
  def mul(scalar)
555
+ unless Secp256k1.native? || Secp256k1.pure_ruby_ct_allowed?
556
+ raise Secp256k1::InsecureOperationError,
557
+ 'mul requires the native C extension for constant-time guarantees. ' \
558
+ 'Set SECP256K1_ALLOW_PURE_RUBY_CT=1 or call Secp256k1.allow_pure_ruby_ct! to override.'
559
+ end
560
+
521
561
  return self.class.infinity if scalar.zero? || infinity?
522
562
 
523
563
  scalar %= N
524
564
  return self.class.infinity if scalar.zero?
525
565
 
526
- jp = Secp256k1.scalar_multiply_wnaf(scalar, @x, @y)
566
+ jp = Secp256k1.scalar_multiply_ct(scalar, @x, @y)
527
567
  affine = Secp256k1.jp_to_affine(jp)
528
568
  return self.class.infinity if affine.nil?
529
569
 
530
570
  self.class.new(affine[0], affine[1])
531
571
  end
532
572
 
533
- # Constant-time scalar multiplication: self * scalar (Montgomery ladder).
573
+ # @deprecated Use {#mul} instead. Alias retained for backward compatibility.
574
+ alias mul_ct mul
575
+
576
+ # Variable-time scalar multiplication: self * scalar (wNAF).
534
577
  #
535
- # Processes all 256 bits unconditionally so execution time does not
536
- # depend on the scalar value. Use this for secret-scalar paths:
537
- # key generation, signing, and ECDH shared-secret derivation.
578
+ # Faster than {#mul} but leaks timing information about the scalar.
579
+ # Use only when the scalar is public (e.g. signature verification,
580
+ # computing known generator multiples). Never use with secret scalars.
538
581
  #
539
- # @param scalar [Integer] the secret scalar multiplier
582
+ # @param scalar [Integer] the public scalar multiplier
540
583
  # @return [Point] the resulting point
541
- def mul_ct(scalar)
584
+ def mul_vt(scalar)
542
585
  return self.class.infinity if scalar.zero? || infinity?
543
586
 
544
587
  scalar %= N
545
588
  return self.class.infinity if scalar.zero?
546
589
 
547
- jp = Secp256k1.scalar_multiply_ct(scalar, @x, @y)
590
+ jp = Secp256k1.scalar_multiply_wnaf(scalar, @x, @y)
548
591
  affine = Secp256k1.jp_to_affine(jp)
549
592
  return self.class.infinity if affine.nil?
550
593
 
@@ -619,8 +662,13 @@ module Secp256k1
619
662
  jp_double jp_add jp_neg scalar_multiply_ct].each do |m|
620
663
  singleton_class.define_method(m, Secp256k1Native.method(m).to_proc)
621
664
  end
665
+
666
+ @native = true
622
667
  rescue LoadError
623
- # Extension not compiled — pure-Ruby fallback, no action needed.
668
+ # Extension not compiled — pure-Ruby fallback.
669
+ warn '[secp256k1-native] Native C extension not loaded — falling back to pure Ruby. ' \
670
+ 'Constant-time operations (mul_ct) will raise unless explicitly allowed. ' \
671
+ 'See: https://sgbett.github.io/secp256k1-native/risks/'
624
672
  end
625
673
  end
626
674
  # rubocop:enable Naming/MethodParameterName, Metrics/ModuleLength
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: secp256k1-native
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Bettison