zold 0.0.7 → 0.0.8

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
  SHA1:
3
- metadata.gz: 7a7e5d899910c588ba9fff4987a7341ecb245dad
4
- data.tar.gz: d1c940ba9d95d8d57e588fb1a6675f9117e86a4b
3
+ metadata.gz: 5306e5717fd5e9e5e42148d1e46446895b24b48d
4
+ data.tar.gz: a47d42f9786ed85e65a35026ccca2f0f292bd9e3
5
5
  SHA512:
6
- metadata.gz: ca614449c84d77fe0b5bce135e33168955bf4f71ee62172a6d01e88cb72890ee04e0128ec86f8f98590d943c5eb325f0db1daa26f2d795002f04b31aae7e7600
7
- data.tar.gz: 12cbff8839076e9314eee7805f3c75d09f3f22ed64516a1ad964d2018ef637cc0ebd249495eca8b86c48530d031704d3d8f299c03b201e91496371e9c2e7ef7d
6
+ metadata.gz: 2c93b3dd2db0fb07b521c0920112f743dff5574219ac6c19f9ebc5ec73126b3f671bb9ca71a53a9128c43455e558c6e4293e3c507efeb468202765783935e1e6
7
+ data.tar.gz: 89a74ea62a573ba9f3eff0481083780a41fe212e5c2cea9b325a3d343168df4b264f8152600290fdcca70e76129653b19c80a7dffaf1255efcc8809e32e8fbed
data/README.md CHANGED
@@ -36,7 +36,7 @@ ZOLD principles include:
36
36
  * There is no mining; the only way to get ZOLD is to receive it from someone else
37
37
  * Only 2<sup>63</sup> numerals (no fractions) can technically be issued
38
38
  * The first wallet belongs to the issuer and may have a negative balance
39
- * A wallet is an XML file
39
+ * A wallet is an plain text file
40
40
  * There is no central ledger, each wallet has its own personal ledger
41
41
  * Each transaction in the ledger is confirmed by [RSA](https://simple.wikipedia.org/wiki/RSA_%28algorithm%29) encryption
42
42
  * The network of communicating nodes maintains wallets of users
@@ -84,40 +84,36 @@ A **transaction** is a money transferring operation between two wallets.
84
84
 
85
85
  A wallet may look like this:
86
86
 
87
- ```xml
88
- <wallet>
89
- <id>123456</id>
90
- <pkey><!-- public RSA key, 256 bytes --></pkey>
91
- <ledger>
92
- [...]
93
- <txn id="35">
94
- <date>2017-07-19T21:24:51.136Z</date>
95
- <beneficiary>927284</beneficiary>
96
- <amount>-560</amount>
97
- <sign><!-- RSA signature of the payer --></sign>
98
- </txn>
99
- </ledger>
100
- </wallet>
87
+ ```text
88
+ 12345678abcdef
89
+ AAAAB3NzaC1yc2EAAAADAQABAAABAQCuLuVr4Tl2sXoN5Zb7b6SKMPrVjLxb...
90
+
91
+ 35;2017-07-19T21:24:51.136Z;98bb82c81735c4ee;-560;SKMPrVjLxbM5oDm0IhniQQy3shF...
101
92
  ```
102
93
 
103
- Wallet `<id>` is an unsigned 32-bit integer.
94
+ Lines are separated by either CR or CRLF, doesn't matter.
95
+
96
+ The fist line is wallet ID, a 64-bit unsigned integer.
97
+
98
+ The second line is a public RSA key of the wallet owner.
99
+
100
+ The third line is empty.
101
+
102
+ Each next line is a transaction and it has four or five fields separated by a semi-colon.
103
+
104
+ The first field is transaction ID, an unsigned 16-bit integer.
104
105
 
105
- Transaction `id` is an unsigned 16-bit integer.
106
+ The second field is its date, in ISO 8601 format.
106
107
 
107
- Transaction `date` is an unsigned 32-bit integer, meaning
108
- milliseconds since
109
- [epoch](https://en.wikipedia.org/wiki/Epoch_%28reference_date%29).
108
+ The third field is the wallet ID of the beneficiary.
110
109
 
111
- All amounts are signed 64-bit integers, where 1ZLD by convention equals to
112
- 2<sup>24</sup> (16,777,216). Thus, the technical capacity
113
- of the currency is 549,755,813,888 (half a trillion).
110
+ The forth field is the amount.
114
111
 
115
- The `<sign>` exists only in transactions with negative `amount`.
116
- It contains an RSA signature of a data block, created by the wallet owner:
117
- `date`, `amount`, `beneficiary` and
118
- 64 bytes of [salt](https://en.wikipedia.org/wiki/Salt_%28cryptography%29).
112
+ The fifth field is an RSA signature of "ID;beneficiary;amount" text.
119
113
 
120
- The list of a few backbone nodes is hard-coded in this Git repository.
114
+ 1ZLD by convention equals to 2<sup>24</sup> (16,777,216).
115
+ Thus, the technical capacity of the currency is
116
+ 549,755,813,888 (half a trillion).
121
117
 
122
118
  ## Architecture
123
119
 
@@ -34,13 +34,22 @@ module Zold
34
34
  raise "Can't find RSA key at #{file} (#{path})" unless File.exist?(path)
35
35
  @body = File.read(path)
36
36
  end
37
- @body = text unless text.nil?
37
+ return if text.nil?
38
+ @body = [
39
+ '-----BEGIN PUBLIC KEY-----',
40
+ text.gsub(/(?<=\G.{64})/, "\n"),
41
+ '-----END PUBLIC KEY-----'
42
+ ].join("\n")
38
43
  end
39
44
 
40
45
  def to_s
41
46
  rsa.to_s.strip
42
47
  end
43
48
 
49
+ def to_pub
50
+ to_s.delete("\n").gsub(/-{5}[ A-Z]+-{5}/, '')
51
+ end
52
+
44
53
  def sign(text)
45
54
  Base64.encode64(rsa.sign(OpenSSL::Digest::SHA256.new, text)).delete("\n")
46
55
  end
@@ -56,7 +65,11 @@ module Zold
56
65
  unless text.start_with?('-----BEGIN')
57
66
  text = OpenSSHKeyConverter.decode_pubkey(text.split[1])
58
67
  end
59
- OpenSSL::PKey::RSA.new(text)
68
+ begin
69
+ OpenSSL::PKey::RSA.new(text)
70
+ rescue OpenSSL::PKey::RSAError => e
71
+ raise "Can't read RSA key (#{e.message}): #{text}"
72
+ end
60
73
  end
61
74
  end
62
75
  end
@@ -23,5 +23,5 @@
23
23
  # Copyright:: Copyright (c) 2018 Zerocracy, Inc.
24
24
  # License:: MIT
25
25
  module Zold
26
- VERSION = '0.0.7'.freeze
26
+ VERSION = '0.0.8'.freeze
27
27
  end
@@ -18,7 +18,6 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
19
  # SOFTWARE.
20
20
 
21
- require 'nokogiri'
22
21
  require 'time'
23
22
 
24
23
  # The wallet.
@@ -33,7 +32,7 @@ module Zold
33
32
  end
34
33
 
35
34
  def to_s
36
- id
35
+ id.to_s
37
36
  end
38
37
 
39
38
  def exists?
@@ -46,30 +45,12 @@ module Zold
46
45
 
47
46
  def init(id, pubkey)
48
47
  raise "File '#{@file}' already exists" if File.exist?(@file)
49
- File.write(
50
- @file,
51
- valid(
52
- Nokogiri::XML::Builder.new do |xml|
53
- xml.wallet do
54
- xml.id_ id.to_s
55
- xml.pkey pubkey.to_s
56
- xml.ledger {}
57
- end
58
- end.doc
59
- )
60
- )
48
+ File.write(@file, "#{id}\n#{pubkey.to_pub}\n\n")
61
49
  end
62
50
 
63
51
  def version
64
- xml = load
65
- ver = 0
66
- unless xml.xpath('/wallet/ledger[txn]').empty?
67
- ver = xml.xpath('/wallet/ledger/txn/@id')
68
- .map(&:to_s)
69
- .map(&:to_i)
70
- .max
71
- end
72
- ver
52
+ all = txns
53
+ all.empty? ? 0 : all.map { |t| t[0] }.map(&:to_i).max
73
54
  end
74
55
 
75
56
  def root?
@@ -77,67 +58,63 @@ module Zold
77
58
  end
78
59
 
79
60
  def id
80
- Id.new(load.xpath('/wallet/id/text()').to_s)
61
+ Id.new(lines[0])
81
62
  end
82
63
 
83
64
  def balance
84
65
  Amount.new(
85
- coins: load.xpath('/wallet/ledger/txn/amount/text()')
86
- .map(&:to_s)
87
- .map(&:to_i)
88
- .inject(0) { |sum, n| sum + n }
66
+ coins: txns.map { |t| t[2] }.map(&:to_i).inject(0) { |sum, n| sum + n }
89
67
  )
90
68
  end
91
69
 
92
70
  def sub(amount, target, pvtkey)
93
- xml = load
94
71
  date = Time.now
95
72
  txn = version + 1
96
- t = xml.xpath('/wallet/ledger')[0].add_child('<txn/>')[0]
97
- t['id'] = txn
98
- t.add_child('<date/>')[0].content = date.iso8601
99
- t.add_child('<amount/>')[0].content = -amount.to_i
100
- t.add_child('<beneficiary/>')[0].content = target
101
- t.add_child('<sign/>')[0].content = pvtkey.sign(
102
- "#{txn} #{amount.to_i} #{target}"
103
- )
104
- save(xml)
105
- { id: txn, date: date, amount: amount, beneficiary: id }
73
+ line = [
74
+ txn,
75
+ date.iso8601,
76
+ -amount.to_i,
77
+ target,
78
+ pvtkey.sign("#{txn};#{amount.to_i};#{target}")
79
+ ].join(';') + "\n"
80
+ File.write(@file, (lines << line).join(''))
81
+ {
82
+ id: txn,
83
+ date: date,
84
+ amount: amount,
85
+ beneficiary: id
86
+ }
106
87
  end
107
88
 
108
89
  def add(txn)
109
- xml = load
110
- t = xml.xpath('/wallet/ledger')[0].add_child('<txn/>')[0]
111
- t['id'] = "/#{txn[:id]}"
112
- t.add_child('<date/>')[0].content = txn[:date].iso8601
113
- t.add_child('<amount/>')[0].content = txn[:amount].to_i
114
- t.add_child('<beneficiary/>')[0].content = txn[:beneficiary]
115
- save(xml).to_s
90
+ line = [
91
+ "/#{txn[:id]}",
92
+ txn[:date].iso8601,
93
+ txn[:amount].to_i,
94
+ txn[:beneficiary]
95
+ ].join(';') + "\n"
96
+ File.write(@file, (lines << line).join(''))
116
97
  end
117
98
 
118
99
  def check(id, amount, beneficiary)
119
- xml = load
120
- txn = xml.xpath("/wallet/ledger/txn[@id='#{id}']")[0]
121
- xamount = Amount.new(
122
- coins: txn.xpath('amount/text()')[0].to_s.to_i
123
- ).mul(-1)
100
+ txn = txns.find { |t| t[0].to_i == id }
101
+ raise "Transaction ##{id} not found" if txn.nil?
102
+ xamount = Amount.new(coins: txn[2].to_i).mul(-1)
124
103
  raise "#{xamount} != #{amount}" if xamount != amount
125
- xbeneficiary = Id.new(txn.xpath('beneficiary/text()')[0].to_s)
104
+ xbeneficiary = Id.new(txn[3].to_s)
126
105
  raise "#{xbeneficiary} != #{beneficiary}" if xbeneficiary != beneficiary
127
- data = "#{id} #{amount.to_i} #{beneficiary}"
128
- valid = Key.new(text: xml.xpath('/wallet/pkey/text()')[0].to_s).verify(
129
- txn.xpath('sign/text()')[0].to_s, data
130
- )
106
+ data = "#{id};#{amount.to_i};#{beneficiary}"
107
+ valid = Key.new(text: lines[1].strip).verify(txn[4], data)
131
108
  raise "Signature is not confirming this data: '#{data}'" unless valid
132
109
  true
133
110
  end
134
111
 
135
112
  def income
136
- load.xpath('/wallet/ledger/txn[amount > 0]').each do |txn|
113
+ txns.select { |t| t[2].to_i > 0 }.each do |t|
137
114
  hash = {
138
- id: txn['id'][1..-1].to_i,
139
- beneficiary: Id.new(txn.xpath('beneficiary/text()')[0].to_s),
140
- amount: Amount.new(coins: txn.xpath('amount/text()')[0].to_s.to_i)
115
+ id: t[0][1..-1].to_i,
116
+ beneficiary: Id.new(t[3]),
117
+ amount: Amount.new(coins: t[2].to_i)
141
118
  }
142
119
  yield hash
143
120
  end
@@ -145,33 +122,13 @@ module Zold
145
122
 
146
123
  private
147
124
 
148
- def load
149
- raise "File '#{@file}' is absent" unless File.exist?(@file)
150
- valid(Nokogiri::XML(File.read(@file)))
151
- end
152
-
153
- def save(xml)
154
- File.write(@file, valid(xml).to_s)
125
+ def txns
126
+ lines.drop(3).map { |t| t.split(';') }
155
127
  end
156
128
 
157
- def valid(xml)
158
- xsd = Nokogiri::XML::Schema(
159
- File.open(
160
- File.join(
161
- File.dirname(__FILE__),
162
- '../../assets/wallet.xsd'
163
- )
164
- )
165
- )
166
- errors = xsd.validate(xml)
167
- unless errors.empty?
168
- errors.each do |error|
169
- puts "#{p} #{error.line}: #{error.message}"
170
- end
171
- puts xml
172
- raise 'XML is not valid'
173
- end
174
- xml
129
+ def lines
130
+ raise "File '#{@file}' is absent" unless File.exist?(@file)
131
+ File.readlines(@file)
175
132
  end
176
133
  end
177
134
  end
@@ -40,7 +40,7 @@ module Zold
40
40
  end
41
41
 
42
42
  def find(id)
43
- Zold::Wallet.new(File.join(@dir, "z-#{id}.xml"))
43
+ Zold::Wallet.new(File.join(@dir, id.to_s))
44
44
  end
45
45
  end
46
46
  end
@@ -37,8 +37,8 @@ class TestCreate < Minitest::Test
37
37
  ).run
38
38
  assert wallet.balance.zero?
39
39
  assert(
40
- File.exist?(File.join(dir, "z-#{wallet.id}.xml")),
41
- "Wallet file not found: #{wallet.id}.xml"
40
+ File.exist?(File.join(dir, wallet.id.to_s)),
41
+ "Wallet file not found: #{wallet.id}"
42
42
  )
43
43
  end
44
44
  end
@@ -29,14 +29,16 @@ require_relative '../lib/zold/key.rb'
29
29
  class TestKey < Minitest::Test
30
30
  def test_reads_public_rsa
31
31
  key = Zold::Key.new(file: 'fixtures/id_rsa.pub')
32
- assert key.to_s.start_with?("-----BEGIN PUBLIC KEY-----\nMIICI")
33
- assert key.to_s.end_with?("EAAQ==\n-----END PUBLIC KEY-----")
32
+ assert key.to_pub.start_with?('MIICI')
33
+ assert key.to_pub.end_with?('EAAQ==')
34
+ assert !key.to_pub.include?("\n")
35
+ assert Zold::Key.new(text: key.to_pub).to_pub.start_with?('MIICI')
34
36
  end
35
37
 
36
38
  def test_reads_private_rsa
37
39
  key = Zold::Key.new(file: 'fixtures/id_rsa')
38
- assert key.to_s.start_with?("-----BEGIN RSA PRIVATE KEY-----\nMIIJJ")
39
- assert key.to_s.end_with?("Sg==\n-----END RSA PRIVATE KEY-----")
40
+ assert key.to_pub.start_with?('MIIJJ')
41
+ assert key.to_pub.end_with?('Sg==')
40
42
  end
41
43
 
42
44
  def test_signs_and_verifies
@@ -48,11 +50,16 @@ class TestKey < Minitest::Test
48
50
  end
49
51
 
50
52
  def test_signs_and_verifies_with_random_key
51
- key = OpenSSL::PKey::RSA.new(2048)
52
- pub = Zold::Key.new(text: key.public_key.to_s)
53
- pvt = Zold::Key.new(text: key.to_s)
54
- text = 'How are you doing, dude?'
55
- signature = pvt.sign(text)
56
- assert pub.verify(signature, text)
53
+ Dir.mktmpdir 'test' do |dir|
54
+ key = OpenSSL::PKey::RSA.new(2048)
55
+ file = File.join(dir, 'temp')
56
+ File.write(file, key.public_key.to_s)
57
+ pub = Zold::Key.new(file: file)
58
+ File.write(file, key.to_s)
59
+ pvt = Zold::Key.new(file: file)
60
+ text = 'How are you doing, dude?'
61
+ signature = pvt.sign(text)
62
+ assert pub.verify(signature, text)
63
+ end
57
64
  end
58
65
  end
@@ -36,13 +36,28 @@ class TestWallet < Minitest::Test
36
36
  wallet = wallet(dir)
37
37
  amount = Zold::Amount.new(zld: 39.99)
38
38
  key = Zold::Key.new(file: 'fixtures/id_rsa')
39
- assert wallet.version.zero?
39
+ assert(
40
+ wallet.version.zero?,
41
+ "Wallet version #{wallet.version} is not equal to zero"
42
+ )
40
43
  wallet.sub(amount, Zold::Id.new, key)
44
+ assert(
45
+ wallet.version == 1,
46
+ "Wallet version #{wallet.version} is not equal to 1"
47
+ )
41
48
  wallet.sub(amount, Zold::Id.new, key)
42
- assert wallet.version == 2
43
49
  assert(
44
- wallet.balance == amount.mul(-2),
45
- "#{wallet.balance} is not equal to #{amount.mul(-2)}"
50
+ wallet.version == 2,
51
+ "Wallet version #{wallet.version} is not equal to 2"
52
+ )
53
+ wallet.sub(amount, Zold::Id.new, key)
54
+ assert(
55
+ wallet.version == 3,
56
+ "Wallet version #{wallet.version} is not equal to 3"
57
+ )
58
+ assert(
59
+ wallet.balance == amount.mul(-3),
60
+ "#{wallet.balance} is not equal to #{amount.mul(-3)}"
46
61
  )
47
62
  end
48
63
  end
@@ -51,7 +66,7 @@ class TestWallet < Minitest::Test
51
66
  Dir.mktmpdir 'test' do |dir|
52
67
  pkey = Zold::Key.new(file: 'fixtures/id_rsa.pub')
53
68
  Dir.chdir(dir) do
54
- file = File.join(dir, 'source.xml')
69
+ file = File.join(dir, 'source')
55
70
  wallet = Zold::Wallet.new(file)
56
71
  id = Zold::Id.new.to_s
57
72
  wallet.init(id, pkey)
@@ -105,7 +120,7 @@ class TestWallet < Minitest::Test
105
120
 
106
121
  def wallet(dir)
107
122
  id = Zold::Id.new
108
- file = File.join(dir, "#{id}.xml")
123
+ file = File.join(dir, id.to_s)
109
124
  wallet = Zold::Wallet.new(file)
110
125
  wallet.init(id, Zold::Key.new(file: 'fixtures/id_rsa.pub'))
111
126
  wallet
@@ -46,7 +46,6 @@ Gem::Specification.new do |s|
46
46
  s.extra_rdoc_files = ['README.md', 'LICENSE.txt']
47
47
  s.add_runtime_dependency 'cucumber', '1.3.17'
48
48
  s.add_runtime_dependency 'haml', '5.0.3'
49
- s.add_runtime_dependency 'nokogiri', '~>1.8'
50
49
  s.add_runtime_dependency 'rainbow', '~>3.0'
51
50
  s.add_runtime_dependency 'rake', '12.0.0'
52
51
  s.add_runtime_dependency 'rubocop', '~>0.52.0'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zold
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-02-05 00:00:00.000000000 Z
11
+ date: 2018-02-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cucumber
@@ -38,20 +38,6 @@ dependencies:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
40
  version: 5.0.3
41
- - !ruby/object:Gem::Dependency
42
- name: nokogiri
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '1.8'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '1.8'
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: rainbow
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -230,7 +216,6 @@ files:
230
216
  - README.md
231
217
  - Rakefile
232
218
  - appveyor.yml
233
- - assets/wallet.xsd
234
219
  - bin/zold
235
220
  - cucumber.yml
236
221
  - deploy.sh
@@ -1,71 +0,0 @@
1
- <?xml version="1.0"?>
2
- <!--
3
- (The MIT License)
4
-
5
- Copyright (c) 2018 Zerocracy, Inc.
6
-
7
- Permission is hereby granted, free of charge, to any person obtaining a copy
8
- of this software and associated documentation files (the 'Software'), to deal
9
- in the Software without restriction, including without limitation the rights
10
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- copies of the Software, and to permit persons to whom the Software is
12
- furnished to do so, subject to the following conditions:
13
-
14
- The above copyright notice and this permission notice shall be included in all
15
- copies or substantial portions of the Software.
16
-
17
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
- SOFTWARE.
24
- -->
25
- <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
26
- <xs:simpleType name="amount">
27
- <xs:restriction base="xs:integer">
28
- <xs:minInclusive value="-9223372036854775808"/>
29
- <xs:maxInclusive value="9223372036854775807"/>
30
- </xs:restriction>
31
- </xs:simpleType>
32
- <xs:simpleType name="wallet">
33
- <xs:restriction base="xs:string">
34
- <xs:pattern value="[0-9a-f]{16}"/>
35
- </xs:restriction>
36
- </xs:simpleType>
37
- <xs:complexType name="txn">
38
- <xs:all>
39
- <xs:element name="beneficiary" type="wallet" minOccurs="1" maxOccurs="1"/>
40
- <xs:element name="date" type="xs:dateTime" minOccurs="1" maxOccurs="1"/>
41
- <xs:element name="sign" type="xs:string" minOccurs="0" maxOccurs="1"/>
42
- <xs:element name="amount" type="amount" minOccurs="1" maxOccurs="1"/>
43
- </xs:all>
44
- <xs:attribute name="id" use="required">
45
- <xs:simpleType>
46
- <xs:restriction base="xs:string">
47
- <xs:pattern value="/?[0-9]+"/>
48
- </xs:restriction>
49
- </xs:simpleType>
50
- </xs:attribute>
51
- </xs:complexType>
52
- <xs:complexType name="ledger">
53
- <xs:sequence>
54
- <xs:element name="txn" type="txn" minOccurs="0" maxOccurs="unbounded"/>
55
- </xs:sequence>
56
- </xs:complexType>
57
- <xs:element name="wallet">
58
- <xs:complexType>
59
- <xs:all>
60
- <xs:element name="id" type="wallet" minOccurs="1" maxOccurs="1"/>
61
- <xs:element name="pkey" type="xs:string" minOccurs="1" maxOccurs="1"/>
62
- <xs:element name="ledger" type="ledger" minOccurs="1" maxOccurs="1">
63
- <xs:unique name="txnUnique">
64
- <xs:selector xpath="./txn"/>
65
- <xs:field xpath="@id"/>
66
- </xs:unique>
67
- </xs:element>
68
- </xs:all>
69
- </xs:complexType>
70
- </xs:element>
71
- </xs:schema>