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 +4 -4
- data/lib/bsv/wallet_interface/file_store.rb +53 -1
- data/lib/bsv/wallet_interface/local_proof_store.rb +42 -0
- data/lib/bsv/wallet_interface/memory_store.rb +18 -0
- data/lib/bsv/wallet_interface/proof_store.rb +32 -0
- data/lib/bsv/wallet_interface/storage_adapter.rb +16 -0
- data/lib/bsv/wallet_interface/version.rb +1 -1
- data/lib/bsv/wallet_interface/wallet_client.rb +43 -2
- data/lib/bsv/wallet_interface.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aeb3dc8d2b6c485d81d28e540b432688b65b370be001f545d7fbe0e2adbff34d
|
|
4
|
+
data.tar.gz: 9f9ce64fe90d7d386c1823c4e698ee268e913870a8192135794d8d6f0ddd7643
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
@@ -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
|
-
|
|
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
|
|
data/lib/bsv/wallet_interface.rb
CHANGED
|
@@ -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.
|
|
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
|