bsv-wallet 0.2.2 → 0.3.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 +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 +44 -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: d1640a355dfd2880bab6814c356768c848786260244726f539db357927c75f67
|
|
4
|
+
data.tar.gz: c0aaf75e827d31dcaeada202b65f28f9b3bb0b89793724b503615d4512447e14
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb8e922ac7496dbb755a6a2e8c1f392cadc78a27c23845045a190ff5d4c4e11b2112394647c40a0f5059c4634f949cc68732115e8327de463bf7eee4d519610c
|
|
7
|
+
data.tar.gz: 9db7cc2da623b3731e903e5544aca2c265a7db9fc0e5b24c200027f11f4e5b11816ba6fc8bb7ac455c37bf748fc642289f01fcbfdc71ac4132192c84d9a11386
|
|
@@ -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,8 @@ 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)
|
|
186
|
+
@storage.store_transaction(tx.txid_hex, tx.to_hex)
|
|
180
187
|
process_internalize_outputs(tx, args[:outputs])
|
|
181
188
|
store_action(tx, args, status: 'completed')
|
|
182
189
|
{ accepted: true }
|
|
@@ -523,7 +530,32 @@ module BSV
|
|
|
523
530
|
|
|
524
531
|
return unless stored[:source_tx_hex]
|
|
525
532
|
|
|
526
|
-
|
|
533
|
+
source_tx = BSV::Transaction::Transaction.from_hex(stored[:source_tx_hex])
|
|
534
|
+
txid_hex = outpoint.split('.').first
|
|
535
|
+
proof = @proof_store.resolve_proof(txid_hex)
|
|
536
|
+
source_tx.merkle_path = proof if proof
|
|
537
|
+
|
|
538
|
+
# Recursively wire ancestors of the source tx so to_beef can build a
|
|
539
|
+
# complete, valid BEEF with the full proof chain.
|
|
540
|
+
wire_source_tx_ancestors(source_tx) unless source_tx.merkle_path
|
|
541
|
+
|
|
542
|
+
input.source_transaction = source_tx
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
def wire_source_tx_ancestors(tx)
|
|
546
|
+
tx.inputs.each do |inp|
|
|
547
|
+
next if inp.source_transaction
|
|
548
|
+
|
|
549
|
+
ancestor_txid_hex = inp.prev_tx_id.reverse.unpack1('H*')
|
|
550
|
+
tx_hex = @storage.find_transaction(ancestor_txid_hex)
|
|
551
|
+
next unless tx_hex
|
|
552
|
+
|
|
553
|
+
ancestor_tx = BSV::Transaction::Transaction.from_hex(tx_hex)
|
|
554
|
+
proof = @proof_store.resolve_proof(ancestor_txid_hex)
|
|
555
|
+
ancestor_tx.merkle_path = proof if proof
|
|
556
|
+
wire_source_tx_ancestors(ancestor_tx) unless ancestor_tx.merkle_path
|
|
557
|
+
inp.source_transaction = ancestor_tx
|
|
558
|
+
end
|
|
527
559
|
end
|
|
528
560
|
|
|
529
561
|
def build_outputs(tx, outputs)
|
|
@@ -651,6 +683,16 @@ module BSV
|
|
|
651
683
|
|
|
652
684
|
# --- Internalize helpers ---
|
|
653
685
|
|
|
686
|
+
def store_proofs_from_beef(beef)
|
|
687
|
+
beef.transactions.each do |beef_tx|
|
|
688
|
+
next unless beef_tx.transaction&.merkle_path
|
|
689
|
+
|
|
690
|
+
txid_hex = beef_tx.transaction.txid_hex
|
|
691
|
+
@proof_store.store_proof(txid_hex, beef_tx.transaction.merkle_path)
|
|
692
|
+
@storage.store_transaction(txid_hex, beef_tx.transaction.to_hex)
|
|
693
|
+
end
|
|
694
|
+
end
|
|
695
|
+
|
|
654
696
|
def extract_subject_transaction(beef)
|
|
655
697
|
return find_by_subject_txid(beef) if beef.subject_txid
|
|
656
698
|
|
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.1
|
|
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
|