bsv-sdk 0.5.0 → 0.6.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: e6f6657530a3e07510c21eeb919dd276a99b680a825b1d63c8e3c05731f36107
4
- data.tar.gz: 0e44275e673ea30e56d4996d1f21e98e8bb22803815da60ab1f32fefcc61b327
3
+ metadata.gz: a39e87386fb16ab953986a95b21a8f09a593798e34f9c11d58a262dd467339ca
4
+ data.tar.gz: cf6bd0ccfb54424409700d4b8d9583c168ecaf80ab707fcd781c8c496120d601
5
5
  SHA512:
6
- metadata.gz: 1e3bbb1cf676054dbf64cfcb94d3edd33e290f9d64734ec828ed1673b97f65ddb25b83e087e6e592ca036d1699a4ded46c974491f6a828b975b2c739e57e214e
7
- data.tar.gz: 0de23da3bb0755d7dcf515f75638286479828c6dac0957532aedbfe28abad6bf8efee8c13b8092e5d05826559fdb16f7c102b84c242885b089022f90b638de05
6
+ metadata.gz: ef3e52bdf9f8d4b351913281c3f19a5ad58eec12b02cc167034b538ec571cd570cd07f0d155daab86de44da722a49307b53a19db3ae0e15ea157d27f63166408
7
+ data.tar.gz: bbf618afe5ad1905843956e6e682d0c3b71ca6fb15ef4c0f05bc8ce5af6094d45f9b8b2e45bb5fdb12aa4f5c8fd37054b54a0824a52e1195b381ddd1b2fc666d
data/CHANGELOG.md CHANGED
@@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.6.0] - 2026-04-04
9
+
10
+ ### Added
11
+
12
+ #### Primitives
13
+
14
+ - **Pure Ruby secp256k1** — native Ruby implementation of secp256k1 elliptic curve operations, ported from the TypeScript reference SDK. Replaces OpenSSL's EC point arithmetic with an OpenSSL compatibility shim — zero consumer code changes required. See [docs/about/secp256k1.md](docs/about/secp256k1.md).
15
+ - Field arithmetic (modular multiplication, inversion, square root) over the secp256k1 prime.
16
+ - Jacobian coordinate point operations (addition, doubling, scalar multiplication).
17
+ - Windowed-NAF (w=5) scalar multiplication with precomputed table caching.
18
+ - SEC 1 point serialisation (compressed and uncompressed).
19
+ - 126 byte-for-byte compliance specs against real OpenSSL.
20
+ - 24 process-isolated integration tests (separate Ruby processes, MD5 file comparison).
21
+
22
+ #### Registry
23
+
24
+ - **Registry client** — `BSV::Registry` module for on-chain definition management.
25
+ - `Client` — register, resolve, list, revoke, and update definitions for protocols, baskets, and certificate types via PushDrop tokens on the overlay network.
26
+ - Per-type overlay topics (`tm_basketmap`, `tm_protomap`, `tm_certmap`) and lookup services matching TS and Go SDKs.
27
+ - Types: `BasketDefinitionData`, `ProtocolDefinitionData`, `CertificateDefinitionData`, `CertificateFieldDescriptor`, `RegisteredDefinition`.
28
+ - Ownership verification before revocation. BEEF Array/String normalisation for wire format compatibility.
29
+
30
+ ### Changed
31
+
32
+ - **OpenSSL usage reduced** — OpenSSL now used only for hashing (SHA/RIPEMD), HMAC, PBKDF2, AES, and constant-time comparison. Elliptic curve operations are pure Ruby.
33
+
8
34
  ## [0.5.0] - 2026-04-04
9
35
 
10
36
  ### Added
data/README.md CHANGED
@@ -34,7 +34,7 @@ These are maintained under the BSV Blockchain organisation and backed by the Bit
34
34
 
35
35
  The BSV Blockchain Libraries Project aims to structure and maintain a middleware layer of the BSV Blockchain technology stack. By facilitating the development and maintenance of core libraries, it serves as an essential toolkit for developers looking to build on the BSV Blockchain.
36
36
 
37
- This Ruby SDK brings maximum compatibility with the official SDK family to the Ruby ecosystem. It was born from a practical need: building an attestation gem ([bsv-attest](https://rubygems.org/gems/bsv-attest)) required a complete, idiomatic Ruby implementation of BSV primitives, script handling, and transaction construction. Rather than wrapping FFI bindings or shelling out to other languages, the SDK implements everything in pure Ruby using only the standard library's OpenSSL bindings.
37
+ This Ruby SDK brings maximum compatibility with the official SDK family to the Ruby ecosystem. It was born from a practical need: building an attestation gem ([bsv-attest](https://rubygems.org/gems/bsv-attest)) required a complete, idiomatic Ruby implementation of BSV primitives, script handling, and transaction construction. Rather than wrapping FFI bindings or shelling out to other languages, the SDK implements everything in pure Ruby. Elliptic curve operations (secp256k1) use a native Ruby implementation ported from the TypeScript reference SDK; OpenSSL is used only for hashing, HMAC, and symmetric encryption.
38
38
 
39
39
  <!-- TODO: Update bsv-attest link once gem documentation is published (see #48) -->
40
40
 
@@ -43,7 +43,7 @@ This Ruby SDK brings maximum compatibility with the official SDK family to the R
43
43
  ### Requirements
44
44
 
45
45
  - Ruby >= 2.7
46
- - No external dependencies beyond Ruby's standard library (`openssl`)
46
+ - No external dependencies beyond Ruby's standard library (`openssl` for hashing, HMAC, PBKDF2, and AES)
47
47
 
48
48
  ### Installation
49
49
 
@@ -101,7 +101,7 @@ puts tx.to_hex
101
101
 
102
102
  ## Features & Deliverables
103
103
 
104
- - **Cryptographic Primitives** — ECDSA signing with RFC 6979 deterministic nonces, Schnorr signatures, ECIES encryption/decryption, Bitcoin Signed Messages. All built on Ruby's stdlib OpenSSL.
104
+ - **Cryptographic Primitives** — ECDSA signing with RFC 6979 deterministic nonces, Schnorr signatures, ECIES encryption/decryption, Bitcoin Signed Messages. Elliptic curve operations use a [pure Ruby secp256k1 implementation](docs/about/secp256k1.md) ported from the TypeScript reference SDK.
105
105
  - **Key Management** — BIP-32 HD key derivation, BIP-39 mnemonic generation (12/24-word phrases), WIF import/export, Base58Check encoding/decoding.
106
106
  - **Script Layer** — Complete opcode set, script parsing and serialisation, type detection and predicates (`p2pkh?`, `p2pk?`, `p2sh?`, `multisig?`, `op_return?`), data extraction (pubkey hashes, script hashes, addresses), and a fluent builder API.
107
107
  - **Script Templates** — Ready-made locking and unlocking script generators for P2PKH, P2PK, P2MS (multisig), and OP_RETURN.
@@ -1,14 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'openssl'
3
+ require_relative 'openssl_ec_shim'
4
4
 
5
5
  module BSV
6
6
  module Primitives
7
7
  # Low-level secp256k1 elliptic curve operations.
8
8
  #
9
- # Wraps +OpenSSL::PKey::EC+ to provide point arithmetic, scalar
10
- # multiplication, and key construction helpers used throughout the SDK.
11
- # All constants and methods operate on the secp256k1 curve.
9
+ # Backed by the pure Ruby {Secp256k1} module via an OpenSSL
10
+ # compatibility shim. The shim preserves the +OpenSSL::PKey::EC+
11
+ # interface so consumer code is unchanged. All constants and
12
+ # methods operate on the secp256k1 curve.
12
13
  module Curve
13
14
  # The secp256k1 curve group.
14
15
  GROUP = OpenSSL::PKey::EC::Group.new('secp256k1')
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ # OpenSSL EC shim — replaces OpenSSL's EC point arithmetic with the pure
4
+ # Ruby {BSV::Primitives::Secp256k1} engine while keeping real OpenSSL for
5
+ # BN, Digest, HMAC, Cipher, etc.
6
+ #
7
+ # Load order matters: real OpenSSL is loaded first so that BN and the
8
+ # symmetric-crypto classes are available, then the EC classes are
9
+ # replaced (not reopened) to delegate to our Secp256k1 module.
10
+
11
+ require 'openssl'
12
+
13
+ # Ensure the Secp256k1 module is loaded before we reference it.
14
+ require_relative 'secp256k1'
15
+
16
+ # -- Define shim classes outside the OpenSSL namespace first, then swap. --
17
+
18
+ # Shim Group for secp256k1.
19
+ class BSVShimECGroup
20
+ def initialize(curve_name)
21
+ raise ArgumentError, "unsupported curve: #{curve_name}" unless curve_name == 'secp256k1'
22
+ end
23
+
24
+ def order
25
+ @order ||= OpenSSL::BN.new(BSV::Primitives::Secp256k1::N.to_s)
26
+ end
27
+
28
+ def generator
29
+ @generator ||= BSVShimECPoint.from_secp_point(self, BSV::Primitives::Secp256k1::Point.generator)
30
+ end
31
+
32
+ def curve_name
33
+ 'secp256k1'
34
+ end
35
+ end
36
+
37
+ # Shim Point wrapping BSV::Primitives::Secp256k1::Point.
38
+ class BSVShimECPoint
39
+ class Error < OpenSSL::OpenSSLError; end
40
+
41
+ attr_reader :group
42
+
43
+ def initialize(group, bn = nil)
44
+ @group = group
45
+ if bn.nil?
46
+ @secp_point = BSV::Primitives::Secp256k1::Point.infinity
47
+ else
48
+ bytes = bn.to_s(2)
49
+ begin
50
+ @secp_point = BSV::Primitives::Secp256k1::Point.from_bytes(bytes)
51
+ rescue ArgumentError => e
52
+ raise Error, e.message
53
+ end
54
+ end
55
+ end
56
+
57
+ def self.from_secp_point(group, secp_point)
58
+ pt = allocate
59
+ pt.instance_variable_set(:@group, group)
60
+ pt.instance_variable_set(:@secp_point, secp_point)
61
+ pt
62
+ end
63
+
64
+ def mul(*args)
65
+ if args.length == 1
66
+ scalar = bn_to_int(args[0])
67
+ result = @secp_point.mul(scalar)
68
+ self.class.from_secp_point(@group, result)
69
+ elsif args.length == 2
70
+ bns = args[0]
71
+ points = args[1]
72
+ result = @secp_point.mul(bn_to_int(bns[0]))
73
+ points.each_with_index do |pt, i|
74
+ term = pt.instance_variable_get(:@secp_point).mul(bn_to_int(bns[i + 1]))
75
+ result = result.add(term)
76
+ end
77
+ self.class.from_secp_point(@group, result)
78
+ else
79
+ raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 1 or 2)"
80
+ end
81
+ end
82
+
83
+ def add(other)
84
+ result = @secp_point.add(other.instance_variable_get(:@secp_point))
85
+ self.class.from_secp_point(@group, result)
86
+ end
87
+
88
+ def to_octet_string(format = :compressed)
89
+ @secp_point.to_octet_string(format)
90
+ end
91
+
92
+ def to_bn(format = :compressed)
93
+ OpenSSL::BN.new(to_octet_string(format), 2)
94
+ end
95
+
96
+ def infinity?
97
+ @secp_point.infinity?
98
+ end
99
+
100
+ def set_to_infinity!
101
+ @secp_point = BSV::Primitives::Secp256k1::Point.infinity
102
+ self
103
+ end
104
+
105
+ private
106
+
107
+ def bn_to_int(bn)
108
+ if bn.is_a?(OpenSSL::BN)
109
+ BSV::Primitives::Secp256k1.bytes_to_int(bn.to_s(2))
110
+ elsif bn.is_a?(Integer)
111
+ bn
112
+ else
113
+ raise ArgumentError, "expected OpenSSL::BN or Integer, got #{bn.class}"
114
+ end
115
+ end
116
+ end
117
+
118
+ # Shim EC key wrapping a public key point.
119
+ class BSVShimEC
120
+ attr_reader :public_key
121
+
122
+ def initialize(der_or_point)
123
+ if der_or_point.is_a?(BSVShimECPoint)
124
+ @public_key = der_or_point
125
+ elsif der_or_point.is_a?(String)
126
+ @public_key = self.class.parse_der(der_or_point)
127
+ else
128
+ raise ArgumentError, "unsupported argument: #{der_or_point.class}"
129
+ end
130
+ end
131
+
132
+ # Parse a DER-encoded EC key and extract the public key point.
133
+ #
134
+ # Note: unlike real OpenSSL, this parser does not validate the curve
135
+ # OID embedded in the DER. This is acceptable because the only callers
136
+ # are {Curve.ec_key_from_private_bytes} and {Curve.ec_key_from_public_bytes},
137
+ # which construct the DER internally with a hardcoded secp256k1 OID.
138
+ # No production code path passes externally-supplied DER to this method.
139
+ # This method exists solely to maintain interface compatibility with the
140
+ # original OpenSSL-backed Curve module and may be removed in a future
141
+ # refactor that eliminates the ec_key_from_* methods.
142
+ def self.parse_der(der)
143
+ der = der.b
144
+ seq = OpenSSL::ASN1.decode(der)
145
+
146
+ if seq.value[0].is_a?(OpenSSL::ASN1::Integer) && seq.value[0].value == 1
147
+ priv_bytes = seq.value[1].value
148
+ scalar = BSV::Primitives::Secp256k1.bytes_to_int(priv_bytes)
149
+ secp_point = BSV::Primitives::Secp256k1::Point.generator.mul(scalar)
150
+ group = BSVShimECGroup.new('secp256k1')
151
+ BSVShimECPoint.from_secp_point(group, secp_point)
152
+ else
153
+ pub_bytes = seq.value[1].value
154
+ pub_bytes = pub_bytes[1..] if pub_bytes.getbyte(0).zero? && pub_bytes.length > 33
155
+ secp_point = BSV::Primitives::Secp256k1::Point.from_bytes(pub_bytes)
156
+ group = BSVShimECGroup.new('secp256k1')
157
+ BSVShimECPoint.from_secp_point(group, secp_point)
158
+ end
159
+ end
160
+ end
161
+
162
+ # -- Replace OpenSSL's EC classes with our shims. --
163
+ # The originals are C-backed and cannot be reopened cleanly (C methods
164
+ # take precedence over Ruby methods). We replace the constants instead.
165
+
166
+ module OpenSSL
167
+ module PKey
168
+ remove_const :EC if const_defined?(:EC)
169
+ EC = BSVShimEC
170
+
171
+ class EC
172
+ remove_const :Group if const_defined?(:Group)
173
+ Group = BSVShimECGroup
174
+
175
+ remove_const :Point if const_defined?(:Point)
176
+ Point = BSVShimECPoint
177
+ end
178
+ end
179
+ end
180
+
181
+ # Ensure the Error class is accessible at the expected path.
182
+ BSVShimECPoint.const_set(:Error, BSVShimECPoint::Error) unless BSVShimECPoint.const_defined?(:Error)