bsv-wallet 0.2.2 → 0.3.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: c1c9a3b59c2d83b14456fcdc7e4eb9f26ad8a5cd4094bc1dd192b26e69903d2b
4
- data.tar.gz: 3fa4584de7d5f3ce782468aa9243ffc80ddc7106b1da6ed11b7d0a97250c2a29
3
+ metadata.gz: aeb3dc8d2b6c485d81d28e540b432688b65b370be001f545d7fbe0e2adbff34d
4
+ data.tar.gz: 9f9ce64fe90d7d386c1823c4e698ee268e913870a8192135794d8d6f0ddd7643
5
5
  SHA512:
6
- metadata.gz: 500bec40cbe5fbf13f325e975522ec467a87d8ddef268a88cc6f4f35f429d75d27b1fd7adbd0f085668d21c79ddef1a19270b5502296e4dcc35fa74031f27b0f
7
- data.tar.gz: b0dd2838de3f000c5dadd73cb9ce3b6e5575e7c224a4397dbdb0ce2cfd73afdd2cb0366e6805faa035a3375560d3fe2b5ede49a82ae77e494957eb9549cd7727
6
+ metadata.gz: 8c49d1e84cc279ce6ec065bcf914d83d16590ded06601459979d0e2cd8084b95d678121fa8c4c36f27c68243ee2337b88857f77d1e09a79f3153e5800a76d54d
7
+ data.tar.gz: 83c9ae27c5ca5b09731d81e9aedbc3c981310675f6d2052a229c2da8215b7e13e95e5a8f72c39418ec20f42e4a95478459394f051c3ab32b99578066ccf0504e
@@ -72,8 +72,26 @@ module BSV
72
72
  result
73
73
  end
74
74
 
75
+ def store_proof(txid, bump_hex)
76
+ super
77
+ save_proofs
78
+ end
79
+
80
+ def store_transaction(txid, tx_hex)
81
+ super
82
+ save_transactions
83
+ end
84
+
75
85
  private
76
86
 
87
+ def proofs_path
88
+ File.join(@dir, 'proofs.json')
89
+ end
90
+
91
+ def transactions_path
92
+ File.join(@dir, 'transactions.json')
93
+ end
94
+
77
95
  def check_permissions
78
96
  dir_mode = File.stat(@dir).mode & 0o777
79
97
  if dir_mode != 0o700
@@ -81,7 +99,7 @@ module BSV
81
99
  'Other users may be able to access wallet data.')
82
100
  end
83
101
 
84
- [actions_path, outputs_path, certificates_path].each do |path|
102
+ [actions_path, outputs_path, certificates_path, proofs_path, transactions_path].each do |path|
85
103
  next unless File.exist?(path)
86
104
 
87
105
  file_mode = File.stat(path).mode & 0o777
@@ -108,6 +126,8 @@ module BSV
108
126
  @actions = load_file(actions_path)
109
127
  @outputs = load_file(outputs_path)
110
128
  @certificates = load_file(certificates_path)
129
+ @proofs = load_proofs_file
130
+ @transactions = load_transactions_file
111
131
  end
112
132
 
113
133
  def load_file(path)
@@ -134,6 +154,38 @@ module BSV
134
154
  write_file(certificates_path, @certificates)
135
155
  end
136
156
 
157
+ def save_proofs
158
+ json = JSON.pretty_generate(@proofs)
159
+ tmp = "#{proofs_path}.tmp"
160
+ File.open(tmp, File::WRONLY | File::CREAT | File::TRUNC, 0o600) { |f| f.write(json) }
161
+ File.rename(tmp, proofs_path)
162
+ end
163
+
164
+ def load_proofs_file
165
+ return {} unless File.exist?(proofs_path)
166
+
167
+ data = JSON.parse(File.read(proofs_path))
168
+ data.is_a?(Hash) ? data : {}
169
+ rescue JSON::ParserError
170
+ {}
171
+ end
172
+
173
+ def save_transactions
174
+ json = JSON.pretty_generate(@transactions)
175
+ tmp = "#{transactions_path}.tmp"
176
+ File.open(tmp, File::WRONLY | File::CREAT | File::TRUNC, 0o600) { |f| f.write(json) }
177
+ File.rename(tmp, transactions_path)
178
+ end
179
+
180
+ def load_transactions_file
181
+ return {} unless File.exist?(transactions_path)
182
+
183
+ data = JSON.parse(File.read(transactions_path))
184
+ data.is_a?(Hash) ? data : {}
185
+ rescue JSON::ParserError
186
+ {}
187
+ end
188
+
137
189
  def write_file(path, data)
138
190
  json = JSON.pretty_generate(stringify_keys_deep(data))
139
191
  tmp = "#{path}.tmp"
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ # Default proof store — persists serialised merkle proofs via a
6
+ # {StorageAdapter}. Requires no external services.
7
+ #
8
+ # @example Using the default local proof store
9
+ # store = BSV::Wallet::MemoryStore.new
10
+ # proofs = BSV::Wallet::LocalProofStore.new(store)
11
+ # proofs.store_proof(txid_hex, merkle_path)
12
+ # proof = proofs.resolve_proof(txid_hex)
13
+ class LocalProofStore
14
+ include ProofStore
15
+
16
+ # @param storage [StorageAdapter] the underlying persistence adapter
17
+ def initialize(storage)
18
+ @storage = storage
19
+ end
20
+
21
+ # Serialise and store a merkle proof.
22
+ #
23
+ # @param txid [String] hex transaction ID
24
+ # @param merkle_path [BSV::Transaction::MerklePath] the proof to store
25
+ # @return [void]
26
+ def store_proof(txid, merkle_path)
27
+ @storage.store_proof(txid, merkle_path.to_hex)
28
+ end
29
+
30
+ # Resolve and deserialise a merkle proof.
31
+ #
32
+ # @param txid [String] hex transaction ID
33
+ # @return [BSV::Transaction::MerklePath, nil] the proof, or nil if unknown
34
+ def resolve_proof(txid)
35
+ bump_hex = @storage.find_proof(txid)
36
+ return unless bump_hex
37
+
38
+ BSV::Transaction::MerklePath.from_hex(bump_hex)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -13,6 +13,8 @@ module BSV
13
13
  @actions = []
14
14
  @outputs = []
15
15
  @certificates = []
16
+ @proofs = {}
17
+ @transactions = {}
16
18
  end
17
19
 
18
20
  def store_action(action_data)
@@ -62,6 +64,22 @@ module BSV
62
64
  filter_certificates(query).length
63
65
  end
64
66
 
67
+ def store_proof(txid, bump_hex)
68
+ @proofs[txid] = bump_hex
69
+ end
70
+
71
+ def find_proof(txid)
72
+ @proofs[txid]
73
+ end
74
+
75
+ def store_transaction(txid, tx_hex)
76
+ @transactions[txid] = tx_hex
77
+ end
78
+
79
+ def find_transaction(txid)
80
+ @transactions[txid]
81
+ end
82
+
65
83
  def delete_certificate(type:, serial_number:, certifier:)
66
84
  idx = @certificates.index do |c|
67
85
  c[:type] == type && c[:serial_number] == serial_number && c[:certifier] == certifier
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ # Duck-typed interface for merkle proof persistence.
6
+ #
7
+ # Include this module in proof store implementations and override all
8
+ # methods. The default implementations raise NotImplementedError.
9
+ #
10
+ # Two implementations ship with the SDK:
11
+ # - {LocalProofStore} — persists proofs via a {StorageAdapter} (default)
12
+ # - Future: ChaintracksProofStore — resolves proofs from an external service
13
+ module ProofStore
14
+ # Store a merkle proof for a transaction.
15
+ #
16
+ # @param _txid [String] hex transaction ID
17
+ # @param _merkle_path [BSV::Transaction::MerklePath] the proof to store
18
+ # @return [void]
19
+ def store_proof(_txid, _merkle_path)
20
+ raise NotImplementedError, "#{self.class}#store_proof not implemented"
21
+ end
22
+
23
+ # Resolve a merkle proof for a transaction.
24
+ #
25
+ # @param _txid [String] hex transaction ID
26
+ # @return [BSV::Transaction::MerklePath, nil] the proof, or nil if unknown
27
+ def resolve_proof(_txid)
28
+ raise NotImplementedError, "#{self.class}#resolve_proof not implemented"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -50,6 +50,22 @@ module BSV
50
50
  def count_certificates(_query)
51
51
  raise NotImplementedError, "#{self.class}#count_certificates not implemented"
52
52
  end
53
+
54
+ def store_proof(_txid, _bump_hex)
55
+ raise NotImplementedError, "#{self.class}#store_proof not implemented"
56
+ end
57
+
58
+ def find_proof(_txid)
59
+ raise NotImplementedError, "#{self.class}#find_proof not implemented"
60
+ end
61
+
62
+ def store_transaction(_txid, _tx_hex)
63
+ raise NotImplementedError, "#{self.class}#store_transaction not implemented"
64
+ end
65
+
66
+ def find_transaction(_txid)
67
+ raise NotImplementedError, "#{self.class}#find_transaction not implemented"
68
+ end
53
69
  end
54
70
  end
55
71
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module BSV
4
4
  module WalletInterface
5
- VERSION = '0.2.2'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
@@ -35,17 +35,22 @@ module BSV
35
35
  # @return [String] the network ('mainnet' or 'testnet')
36
36
  attr_reader :network
37
37
 
38
+ # @return [ProofStore] the merkle proof persistence store
39
+ attr_reader :proof_store
40
+
38
41
  # @param key [BSV::Primitives::PrivateKey, String, KeyDeriver] signing key
39
42
  # @param storage [StorageAdapter] persistence adapter (default: FileStore).
40
43
  # Use +storage: MemoryStore.new+ for tests.
41
44
  # @param network [String] 'mainnet' (default) or 'testnet'
42
45
  # @param chain_provider [ChainProvider] blockchain data provider (default: NullChainProvider)
46
+ # @param proof_store [ProofStore, nil] merkle proof store (default: LocalProofStore backed by storage)
43
47
  # @param http_client [#request, nil] injectable HTTP client for certificate issuance
44
- def initialize(key, storage: FileStore.new, network: 'mainnet', chain_provider: NullChainProvider.new, http_client: nil)
48
+ def initialize(key, storage: FileStore.new, network: 'mainnet', chain_provider: NullChainProvider.new, proof_store: nil, http_client: nil)
45
49
  super(key)
46
50
  @storage = storage
47
51
  @network = network
48
52
  @chain_provider = chain_provider
53
+ @proof_store = proof_store || LocalProofStore.new(storage)
49
54
  @http_client = http_client
50
55
  @pending = {}
51
56
  end
@@ -177,6 +182,7 @@ module BSV
177
182
  beef = BSV::Transaction::Beef.from_binary(beef_binary)
178
183
  tx = extract_subject_transaction(beef)
179
184
 
185
+ store_proofs_from_beef(beef)
180
186
  process_internalize_outputs(tx, args[:outputs])
181
187
  store_action(tx, args, status: 'completed')
182
188
  { accepted: true }
@@ -523,7 +529,32 @@ module BSV
523
529
 
524
530
  return unless stored[:source_tx_hex]
525
531
 
526
- input.source_transaction = BSV::Transaction::Transaction.from_hex(stored[:source_tx_hex])
532
+ source_tx = BSV::Transaction::Transaction.from_hex(stored[:source_tx_hex])
533
+ txid_hex = outpoint.split('.').first
534
+ proof = @proof_store.resolve_proof(txid_hex)
535
+ source_tx.merkle_path = proof if proof
536
+
537
+ # Recursively wire ancestors of the source tx so to_beef can build a
538
+ # complete, valid BEEF with the full proof chain.
539
+ wire_source_tx_ancestors(source_tx) unless source_tx.merkle_path
540
+
541
+ input.source_transaction = source_tx
542
+ end
543
+
544
+ def wire_source_tx_ancestors(tx)
545
+ tx.inputs.each do |inp|
546
+ next if inp.source_transaction
547
+
548
+ ancestor_txid_hex = inp.prev_tx_id.reverse.unpack1('H*')
549
+ tx_hex = @storage.find_transaction(ancestor_txid_hex)
550
+ next unless tx_hex
551
+
552
+ ancestor_tx = BSV::Transaction::Transaction.from_hex(tx_hex)
553
+ proof = @proof_store.resolve_proof(ancestor_txid_hex)
554
+ ancestor_tx.merkle_path = proof if proof
555
+ wire_source_tx_ancestors(ancestor_tx) unless ancestor_tx.merkle_path
556
+ inp.source_transaction = ancestor_tx
557
+ end
527
558
  end
528
559
 
529
560
  def build_outputs(tx, outputs)
@@ -651,6 +682,16 @@ module BSV
651
682
 
652
683
  # --- Internalize helpers ---
653
684
 
685
+ def store_proofs_from_beef(beef)
686
+ beef.transactions.each do |beef_tx|
687
+ next unless beef_tx.transaction&.merkle_path
688
+
689
+ txid_hex = beef_tx.transaction.txid_hex
690
+ @proof_store.store_proof(txid_hex, beef_tx.transaction.merkle_path)
691
+ @storage.store_transaction(txid_hex, beef_tx.transaction.to_hex)
692
+ end
693
+ end
694
+
654
695
  def extract_subject_transaction(beef)
655
696
  return find_by_subject_txid(beef) if beef.subject_txid
656
697
 
@@ -14,6 +14,8 @@ module BSV
14
14
  autoload :StorageAdapter, 'bsv/wallet_interface/storage_adapter'
15
15
  autoload :MemoryStore, 'bsv/wallet_interface/memory_store'
16
16
  autoload :FileStore, 'bsv/wallet_interface/file_store'
17
+ autoload :ProofStore, 'bsv/wallet_interface/proof_store'
18
+ autoload :LocalProofStore, 'bsv/wallet_interface/local_proof_store'
17
19
  autoload :ChainProvider, 'bsv/wallet_interface/chain_provider'
18
20
  autoload :NullChainProvider, 'bsv/wallet_interface/null_chain_provider'
19
21
  autoload :WalletClient, 'bsv/wallet_interface/wallet_client'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bsv-wallet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Bettison
@@ -55,8 +55,10 @@ files:
55
55
  - lib/bsv/wallet_interface/file_store.rb
56
56
  - lib/bsv/wallet_interface/interface.rb
57
57
  - lib/bsv/wallet_interface/key_deriver.rb
58
+ - lib/bsv/wallet_interface/local_proof_store.rb
58
59
  - lib/bsv/wallet_interface/memory_store.rb
59
60
  - lib/bsv/wallet_interface/null_chain_provider.rb
61
+ - lib/bsv/wallet_interface/proof_store.rb
60
62
  - lib/bsv/wallet_interface/proto_wallet.rb
61
63
  - lib/bsv/wallet_interface/storage_adapter.rb
62
64
  - lib/bsv/wallet_interface/validators.rb