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 +4 -4
- data/CHANGELOG.md +26 -0
- data/README.md +3 -3
- data/lib/bsv/primitives/curve.rb +5 -4
- data/lib/bsv/primitives/openssl_ec_shim.rb +182 -0
- data/lib/bsv/primitives/secp256k1.rb +504 -0
- data/lib/bsv/primitives.rb +1 -0
- data/lib/bsv/registry/client.rb +582 -0
- data/lib/bsv/registry/constants.rb +39 -0
- data/lib/bsv/registry/types.rb +227 -0
- data/lib/bsv/registry.rb +19 -0
- data/lib/bsv/version.rb +1 -1
- data/lib/bsv-sdk.rb +1 -0
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a39e87386fb16ab953986a95b21a8f09a593798e34f9c11d58a262dd467339ca
|
|
4
|
+
data.tar.gz: cf6bd0ccfb54424409700d4b8d9583c168ecaf80ab707fcd781c8c496120d601
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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.
|
|
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.
|
data/lib/bsv/primitives/curve.rb
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
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)
|