zold 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
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>