bsv-sdk 0.2.0 → 0.2.1

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: 83e581aac74bb9865acd4dac19b8e7422f8a018f7fea7cd9085eb6ad58add6ca
4
- data.tar.gz: 7f9bd7d2b7902dc080943a4b4ac30526e82ba38a0d278be14b07d575a8f52ade
3
+ metadata.gz: d8f24ed29fd0a8beb572b3737c3bbfaba5bf939c5fe20d485306e5e4374587c1
4
+ data.tar.gz: 53746a1cdad1370718892805b76c763fbe58a73ef057bd65a1eaa4b1b120c7bd
5
5
  SHA512:
6
- metadata.gz: a2ad1b26a36e2c052f4109b4ec3649bfdc1f3efd08d0e0e8b08b054a7f61b0a0ae2f777f0da3d4445259681e06b0dd31dc74b252ae0ed8c10f5ff11f67963917
7
- data.tar.gz: '08955c85b4bee8ffcc1c7e84e775c3306ccf8e573103f2347072f86bcd67633abd96193fe516484e94f4cd047edaad5cda52b6612dabbceade6b05dd272acd09'
6
+ metadata.gz: 93590d116e66151f51af5167bb405b2d91cd2997f685fad0d684cd07f5231aa1387d57d71852e5464f3f222d1b471534d22ab6633c75d188f9080c1718f834a0
7
+ data.tar.gz: b26e9a26837af3ce9426311050c0e2fbbdaac9ce4d04a7987c123ef189ec6528e779c69bd7b77605e25df70c1510327a71b7843151f50eb710b2b63a788eeae2
data/CHANGELOG.md CHANGED
@@ -5,6 +5,22 @@ 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.2.1] - 2026-03-07
9
+
10
+ ### Fixed
11
+
12
+ - Truncated OP_PUSHDATA1/2/4 scripts now raise `ArgumentError` instead of crashing with `TypeError`
13
+ - `Transaction#to_beef` uses `merge_bump` to correctly handle multiple ancestors at the same block height
14
+ - `PrivateKey#derive_child` uses `BN.mod_add` instead of Integer roundtrip for modular addition
15
+ - Fixed txid byte-order documentation (display order, not internal order)
16
+
17
+ ### Testing
18
+
19
+ - FORKID enforcement spec verifying interpreter rejects signatures without SIGHASH_FORKID
20
+ - ExtendedKey fingerprint chain integrity across 3-generation derivation
21
+ - Mnemonic entropy round-trip across all 5 valid entropy lengths
22
+ - BEEF spec for multiple ancestors at the same block height
23
+
8
24
  ## [0.2.0] - 2026-03-07
9
25
 
10
26
  ### Added
@@ -158,8 +158,7 @@ module BSV
158
158
  shared = derive_shared_secret(public_key)
159
159
  hmac = Digest.hmac_sha256(shared.compressed, invoice_number.encode('UTF-8'))
160
160
  hmac_bn = OpenSSL::BN.new(hmac.unpack1('H*'), 16)
161
- child_bn = (@bn + hmac_bn).to_i % Curve::N.to_i
162
- PrivateKey.new(OpenSSL::BN.new(child_bn.to_s))
161
+ PrivateKey.new(@bn.mod_add(hmac_bn, Curve::N))
163
162
  end
164
163
 
165
164
  # Sign a 32-byte hash using deterministic ECDSA (RFC 6979).
@@ -391,22 +391,26 @@ module BSV
391
391
  pos += 1
392
392
 
393
393
  if opcode.positive? && opcode <= 0x4b
394
+ raise ArgumentError, "truncated script: need #{opcode} data bytes at offset #{pos}" if pos + opcode > raw.bytesize
394
395
  data = raw.byteslice(pos, opcode)
395
396
  pos += opcode
396
397
  result << Chunk.new(opcode: opcode, data: data)
397
398
  elsif opcode == Opcodes::OP_PUSHDATA1
399
+ raise ArgumentError, "truncated script: OP_PUSHDATA1 missing length byte at offset #{pos}" if pos >= raw.bytesize
398
400
  len = raw.getbyte(pos)
399
401
  pos += 1
400
402
  data = raw.byteslice(pos, len)
401
403
  pos += len
402
404
  result << Chunk.new(opcode: opcode, data: data)
403
405
  elsif opcode == Opcodes::OP_PUSHDATA2
406
+ raise ArgumentError, "truncated script: OP_PUSHDATA2 needs 2 length bytes at offset #{pos}" if pos + 2 > raw.bytesize
404
407
  len = raw.byteslice(pos, 2).unpack1('v')
405
408
  pos += 2
406
409
  data = raw.byteslice(pos, len)
407
410
  pos += len
408
411
  result << Chunk.new(opcode: opcode, data: data)
409
412
  elsif opcode == Opcodes::OP_PUSHDATA4
413
+ raise ArgumentError, "truncated script: OP_PUSHDATA4 needs 4 length bytes at offset #{pos}" if pos + 4 > raw.bytesize
410
414
  len = raw.byteslice(pos, 4).unpack1('V')
411
415
  pos += 4
412
416
  data = raw.byteslice(pos, len)
@@ -63,7 +63,7 @@ module BSV
63
63
 
64
64
  # The transaction ID for this entry.
65
65
  #
66
- # @return [String, nil] 32-byte txid in internal byte order
66
+ # @return [String, nil] 32-byte txid in display byte order
67
67
  def txid
68
68
  case @format
69
69
  when FORMAT_TXID_ONLY
@@ -210,7 +210,7 @@ module BSV
210
210
 
211
211
  # Find a transaction in the bundle by its transaction ID.
212
212
  #
213
- # @param txid [String] 32-byte txid in internal byte order
213
+ # @param txid [String] 32-byte txid in display byte order
214
214
  # @return [Transaction, nil] the matching transaction, or nil
215
215
  def find_transaction(txid)
216
216
  @transactions.each do |beef_tx|
@@ -221,7 +221,7 @@ module BSV
221
221
 
222
222
  # Find the merkle path (BUMP) for a transaction by its txid.
223
223
  #
224
- # @param txid [String] 32-byte txid in internal byte order
224
+ # @param txid [String] 32-byte txid in display byte order
225
225
  # @return [MerklePath, nil] the merkle path, or nil if not found
226
226
  def find_bump(txid)
227
227
  bt = @transactions.find { |entry| entry.txid == txid && entry.format == FORMAT_RAW_TX_AND_BUMP }
@@ -232,7 +232,7 @@ module BSV
232
232
 
233
233
  # Find a transaction with all source_transactions wired for signing.
234
234
  #
235
- # @param txid [String] 32-byte txid in internal byte order
235
+ # @param txid [String] 32-byte txid in display byte order
236
236
  # @return [Transaction, nil] the transaction with wired inputs, or nil
237
237
  def find_transaction_for_signing(txid)
238
238
  tx = find_transaction(txid)
@@ -245,7 +245,7 @@ module BSV
245
245
  # Find a transaction and recursively wire its ancestry (source transactions
246
246
  # and merkle paths) for atomic proof validation.
247
247
  #
248
- # @param txid [String] 32-byte txid in internal byte order
248
+ # @param txid [String] 32-byte txid in display byte order
249
249
  # @return [Transaction, nil] the transaction with full proof tree, or nil
250
250
  def find_atomic_transaction(txid)
251
251
  tx = find_transaction(txid)
@@ -381,7 +381,7 @@ module BSV
381
381
 
382
382
  # Convert a transaction entry to TXID-only format.
383
383
  #
384
- # @param txid [String] 32-byte txid in internal byte order
384
+ # @param txid [String] 32-byte txid in display byte order
385
385
  # @return [BeefTx, nil] the converted entry, or nil if not found
386
386
  def make_txid_only(txid)
387
387
  idx = @transactions.index { |bt| bt.txid == txid }
@@ -557,7 +557,7 @@ module BSV
557
557
 
558
558
  # Wire inputs to ancestors already in the map (BEEF is dependency-ordered)
559
559
  beef_tx.transaction.inputs.each do |input|
560
- # prev_tx_id is internal byte order; txid keys are display order (reversed)
560
+ # prev_tx_id is wire byte order; txid keys are display byte order (reversed)
561
561
  source = tx_map[input.prev_tx_id.reverse]
562
562
  input.source_transaction = source if source
563
563
  end
@@ -296,25 +296,15 @@ module BSV
296
296
  # @return [String] raw BEEF V2 binary
297
297
  def to_beef
298
298
  beef = Beef.new
299
- bump_map = {}
300
299
  ancestors = collect_ancestors
301
300
 
302
301
  ancestors.each do |tx|
303
- # Collect BUMPs
304
- if tx.merkle_path
305
- height = tx.merkle_path.block_height
306
- unless bump_map.key?(height)
307
- bump_map[height] = beef.bumps.length
308
- beef.bumps << tx.merkle_path
309
- end
310
- end
311
-
312
- # Add transaction in dependency order
313
302
  entry = if tx.merkle_path
303
+ bump_idx = beef.merge_bump(tx.merkle_path)
314
304
  Beef::BeefTx.new(
315
305
  format: Beef::FORMAT_RAW_TX_AND_BUMP,
316
306
  transaction: tx,
317
- bump_index: bump_map[tx.merkle_path.block_height]
307
+ bump_index: bump_idx
318
308
  )
319
309
  else
320
310
  Beef::BeefTx.new(
@@ -356,9 +346,13 @@ module BSV
356
346
 
357
347
  # --- Transaction ID ---
358
348
 
359
- # Compute the transaction ID (double-SHA-256 of the serialised tx, reversed).
349
+ # Compute the transaction ID (double-SHA-256 of the serialised tx, byte-reversed).
350
+ #
351
+ # Returns display byte order (reversed from the natural hash).
352
+ # Compare with {TransactionInput#prev_tx_id} which stores wire byte
353
+ # order (natural hash). Use +.reverse+ to convert between the two.
360
354
  #
361
- # @return [String] 32-byte transaction ID in internal byte order
355
+ # @return [String] 32-byte transaction ID in display byte order
362
356
  def txid
363
357
  BSV::Primitives::Digest.sha256d(to_binary).reverse
364
358
  end
data/lib/bsv/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BSV
4
- VERSION = '0.2.0'
4
+ VERSION = '0.2.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bsv-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Bettison