dropzone_ruby 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/Drop Zone - An Anonymous Peer-To-Peer Local Contraband Marketplace.pdf +0 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +69 -0
- data/README.md +62 -0
- data/bin/dropzone +487 -0
- data/dropzone-screenshot.jpg +0 -0
- data/dropzone_ruby.gemspec +31 -0
- data/lib/blockrio_ext.rb +52 -0
- data/lib/dropzone/buyer.rb +21 -0
- data/lib/dropzone/command.rb +488 -0
- data/lib/dropzone/communication.rb +43 -0
- data/lib/dropzone/connection.rb +312 -0
- data/lib/dropzone/invoice.rb +23 -0
- data/lib/dropzone/item.rb +160 -0
- data/lib/dropzone/listing.rb +64 -0
- data/lib/dropzone/message_base.rb +178 -0
- data/lib/dropzone/payment.rb +36 -0
- data/lib/dropzone/profile.rb +86 -0
- data/lib/dropzone/record_base.rb +34 -0
- data/lib/dropzone/seller.rb +21 -0
- data/lib/dropzone/session.rb +161 -0
- data/lib/dropzone/state_accumulator.rb +39 -0
- data/lib/dropzone/version.rb +4 -0
- data/lib/dropzone_ruby.rb +14 -0
- data/lib/veto_checks.rb +74 -0
- data/spec/bitcoin_spec.rb +115 -0
- data/spec/buyer_profile_spec.rb +279 -0
- data/spec/buyer_spec.rb +109 -0
- data/spec/command_spec.rb +353 -0
- data/spec/config.yml +5 -0
- data/spec/invoice_spec.rb +129 -0
- data/spec/item_spec.rb +294 -0
- data/spec/lib/fake_connection.rb +97 -0
- data/spec/listing_spec.rb +150 -0
- data/spec/payment_spec.rb +152 -0
- data/spec/seller_profile_spec.rb +290 -0
- data/spec/seller_spec.rb +120 -0
- data/spec/session_spec.rb +303 -0
- data/spec/sham/buyer.rb +5 -0
- data/spec/sham/invoice.rb +5 -0
- data/spec/sham/item.rb +8 -0
- data/spec/sham/payment.rb +13 -0
- data/spec/sham/seller.rb +7 -0
- data/spec/spec_helper.rb +49 -0
- metadata +267 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
module Dropzone
|
2
|
+
class Listing < RecordBase
|
3
|
+
include StateAccumulator
|
4
|
+
|
5
|
+
attr_reader :txid, :create_item
|
6
|
+
|
7
|
+
self.message_types = 'ITUPDT'
|
8
|
+
|
9
|
+
state_attr :description, :price_currency, :price_in_units, :expiration_in
|
10
|
+
|
11
|
+
def initialize(txid)
|
12
|
+
@txid = txid
|
13
|
+
|
14
|
+
item = Item.find txid
|
15
|
+
@create_item = item if item && item.valid? && item.message_type == 'ITCRTE'
|
16
|
+
|
17
|
+
if create_item
|
18
|
+
attrs_from create_item
|
19
|
+
|
20
|
+
messages(start_block: create_item.block_height).reverse.each{ |item|
|
21
|
+
attrs_from item if item.create_txid == txid}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def found?
|
26
|
+
!@create_item.nil?
|
27
|
+
end
|
28
|
+
|
29
|
+
def expiration_at
|
30
|
+
create_item.block_height+expiration_in
|
31
|
+
end
|
32
|
+
|
33
|
+
def addr; from_create :sender_addr; end
|
34
|
+
def latitude; from_create :latitude; end
|
35
|
+
def longitude; from_create :longitude; end
|
36
|
+
def radius; from_create :radius; end
|
37
|
+
|
38
|
+
def seller_profile
|
39
|
+
@seller_profile ||= SellerProfile.new addr if addr
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def from_create(attr)
|
45
|
+
create_item.send attr if create_item
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Listing::Validator < ValidatorBase
|
50
|
+
validate :must_have_active_seller
|
51
|
+
validate :must_have_created_item
|
52
|
+
|
53
|
+
def must_have_active_seller(listing)
|
54
|
+
errors.add :seller_profile, "invalid or missing" if (
|
55
|
+
listing.seller_profile.nil? || !listing.seller_profile.valid? ||
|
56
|
+
!listing.seller_profile.active? )
|
57
|
+
end
|
58
|
+
|
59
|
+
def must_have_created_item(listing)
|
60
|
+
errors.add :create_item, "invalid or missing" unless (
|
61
|
+
listing.create_item && listing.create_item.valid? )
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
module Dropzone
|
2
|
+
module MessageValidations
|
3
|
+
def self.included(base)
|
4
|
+
base.validates :receiver_addr, presence: true
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
module ProfileValidations
|
9
|
+
def self.included(base)
|
10
|
+
base.validates :receiver_addr, equals_attribute: { attribute: :sender_addr },
|
11
|
+
unless: "self.transfer_pkey", if: "self.sender_addr"
|
12
|
+
|
13
|
+
base.validates :transfer_pkey, equals_attribute: { attribute: :receiver_addr,
|
14
|
+
unless: "self.transfer_pkey.nil? || self.transfer_pkey == 0" }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module BillingValidations
|
19
|
+
def self.included(base)
|
20
|
+
base.validates :receiver_addr, doesnt_equal_attribute: { attribute: :sender_addr },
|
21
|
+
if: "self.sender_addr"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class MessageBase < RecordBase
|
26
|
+
DEFAULT_TIP = 20_000
|
27
|
+
|
28
|
+
attr_reader :receiver_addr, :sender_addr, :message_type, :block_height, :txid
|
29
|
+
|
30
|
+
def initialize(attrs = {})
|
31
|
+
data = attrs.delete(:data)
|
32
|
+
|
33
|
+
attrs.merge(data_hash_from_hex(data)).each do |attr, value|
|
34
|
+
instance_variable_set '@%s' % attr, value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def save!(private_key)
|
39
|
+
self.blockchain.save! to_transaction, private_key
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_transaction
|
43
|
+
{receiver_addr: receiver_addr, data: data_to_hex,
|
44
|
+
tip: MessageBase.default_tip }
|
45
|
+
end
|
46
|
+
|
47
|
+
def data_to_hex
|
48
|
+
data_to_hash.inject(message_type.dup) do |ret, (key, value)|
|
49
|
+
value_hex = case
|
50
|
+
when value.nil?
|
51
|
+
nil
|
52
|
+
when self.class.is_attr_int?(key)
|
53
|
+
Bitcoin::Protocol.pack_var_int(value.to_i)
|
54
|
+
when self.class.is_attr_pkey?(key)
|
55
|
+
Bitcoin::Protocol.pack_var_string(
|
56
|
+
(value == 0) ? 0.chr :
|
57
|
+
[anynet_for_address(:hash160_from_address, value)].pack('H*'))
|
58
|
+
else
|
59
|
+
Bitcoin::Protocol.pack_var_string([value.to_s].pack('a*'))
|
60
|
+
end
|
61
|
+
|
62
|
+
(value_hex.nil?) ? ret :
|
63
|
+
ret << Bitcoin::Protocol.pack_var_string(key.to_s) << value_hex
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def data_to_hash
|
68
|
+
self.class.message_attribs.inject({}) do |ret , (short, full)|
|
69
|
+
ret.merge(short => self.send(full))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def anynet_for_address(method, addr)
|
76
|
+
start_network = Bitcoin.network_name
|
77
|
+
|
78
|
+
begin
|
79
|
+
Bitcoin.network = (/\A1/.match addr) ? :bitcoin : :testnet3
|
80
|
+
|
81
|
+
return Bitcoin.send(method, addr)
|
82
|
+
ensure
|
83
|
+
Bitcoin.network = start_network
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def data_hash_from_hex(data)
|
88
|
+
return {} unless /\A(.{6})(.*)/m.match data
|
89
|
+
|
90
|
+
message_type, pairs, data = $1, $2, {}
|
91
|
+
|
92
|
+
while(pairs.length > 0) do
|
93
|
+
short_key, pairs = Bitcoin::Protocol.unpack_var_string(pairs)
|
94
|
+
|
95
|
+
value, pairs = (self.class.is_attr_int? short_key.to_sym) ?
|
96
|
+
Bitcoin::Protocol.unpack_var_int(pairs) :
|
97
|
+
Bitcoin::Protocol.unpack_var_string(pairs)
|
98
|
+
|
99
|
+
if self.class.is_attr_pkey?(short_key.to_sym) && value
|
100
|
+
value = (value == 0.chr) ? 0 :
|
101
|
+
anynet_for_address(:hash160_to_address, value.unpack('H*')[0])
|
102
|
+
end
|
103
|
+
|
104
|
+
full_key = self.class.message_attribs[short_key.to_sym]
|
105
|
+
data[full_key] = value
|
106
|
+
end
|
107
|
+
|
108
|
+
data
|
109
|
+
end
|
110
|
+
|
111
|
+
class << self
|
112
|
+
attr_writer :default_tip
|
113
|
+
|
114
|
+
def default_tip
|
115
|
+
@default_tip || DEFAULT_TIP
|
116
|
+
end
|
117
|
+
|
118
|
+
def message_attribs
|
119
|
+
@message_attribs
|
120
|
+
end
|
121
|
+
|
122
|
+
def is_attr_int?(attr)
|
123
|
+
@message_integers && @message_integers.include?(attr)
|
124
|
+
end
|
125
|
+
|
126
|
+
def is_attr_pkey?(attr)
|
127
|
+
@message_pkeys && @message_pkeys.include?(attr)
|
128
|
+
end
|
129
|
+
|
130
|
+
def message_type(type)
|
131
|
+
@types_include ||= [type]
|
132
|
+
|
133
|
+
define_method(:message_type){ type }
|
134
|
+
end
|
135
|
+
|
136
|
+
def attr_message(attribs)
|
137
|
+
@message_attribs ||= {}
|
138
|
+
@message_attribs.merge! attribs
|
139
|
+
|
140
|
+
attribs.each{ |short_attr, full_attr| attr_reader full_attr }
|
141
|
+
end
|
142
|
+
|
143
|
+
def attr_message_int(attribs)
|
144
|
+
@message_integers ||= []
|
145
|
+
@message_integers += attribs.keys
|
146
|
+
|
147
|
+
attr_message attribs
|
148
|
+
end
|
149
|
+
|
150
|
+
def attr_message_pkey(attribs)
|
151
|
+
@message_pkeys ||= []
|
152
|
+
@message_pkeys += attribs.keys
|
153
|
+
|
154
|
+
attr_message attribs
|
155
|
+
end
|
156
|
+
|
157
|
+
def find(txid)
|
158
|
+
tx = RecordBase.blockchain.tx_by_id txid
|
159
|
+
tx ? self.new(tx) : nil
|
160
|
+
end
|
161
|
+
|
162
|
+
def types_include?(type)
|
163
|
+
@types_include && @types_include.include?(type)
|
164
|
+
end
|
165
|
+
|
166
|
+
def new_message_from(tx)
|
167
|
+
@messages ||= ObjectSpace.each_object(Class).select {|klass|
|
168
|
+
klass < Dropzone::MessageBase }
|
169
|
+
|
170
|
+
if /\A([a-z0-9]{6})/i.match tx[:data]
|
171
|
+
message_klass = @messages.find{|klass| klass.types_include? $1}
|
172
|
+
(message_klass) ? message_klass.new(tx) : nil
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Dropzone
|
2
|
+
class Payment < MessageBase
|
3
|
+
attr_message d: :description, t: :invoice_txid
|
4
|
+
attr_message_int q: :delivery_quality, p: :product_quality,
|
5
|
+
c: :communications_quality
|
6
|
+
|
7
|
+
def invoice
|
8
|
+
@invoice ||= Invoice.find invoice_txid if invoice_txid
|
9
|
+
end
|
10
|
+
|
11
|
+
message_type 'INPAID'
|
12
|
+
end
|
13
|
+
|
14
|
+
class Payment::Validator < ValidatorBase
|
15
|
+
include MessageValidations
|
16
|
+
include BillingValidations
|
17
|
+
|
18
|
+
validates :message_type, format: /\AINPAID\Z/
|
19
|
+
|
20
|
+
validates_if_present :description, is_string: true
|
21
|
+
validates_if_present :invoice_txid, is_string: true
|
22
|
+
|
23
|
+
[:delivery_quality,:product_quality,:communications_quality ].each do |attr|
|
24
|
+
validates_if_present attr, integer: true, inclusion: 0..8
|
25
|
+
end
|
26
|
+
|
27
|
+
validate :must_have_corresponding_invoice
|
28
|
+
|
29
|
+
def must_have_corresponding_invoice(payment)
|
30
|
+
invoice = payment.invoice
|
31
|
+
|
32
|
+
errors.add :invoice_txid, "can't be found" if ( invoice.nil? ||
|
33
|
+
!invoice.valid? || (invoice.sender_addr != payment.receiver_addr) )
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Dropzone
|
2
|
+
class Profile < RecordBase
|
3
|
+
include StateAccumulator
|
4
|
+
|
5
|
+
attr_reader :addr, :transfer_pkey, :prior_profile
|
6
|
+
|
7
|
+
def initialize(addr)
|
8
|
+
@addr = addr
|
9
|
+
|
10
|
+
messages.reverse.each_with_index do |seller, i|
|
11
|
+
# There is a bit of extra logic if the seller profile was transferred
|
12
|
+
# from elsewhere
|
13
|
+
if i == 0 && seller.transfer_pkey
|
14
|
+
# Load the profile from the prior address and pop it off the stack
|
15
|
+
@prior_profile = self.class.new seller.sender_addr
|
16
|
+
|
17
|
+
# It's possible the prior profile was invalid
|
18
|
+
break unless @prior_profile.valid?
|
19
|
+
|
20
|
+
# And it's possible the prior profile was deactivated or not
|
21
|
+
# transferred to us:
|
22
|
+
break unless @prior_profile.transfer_pkey == addr
|
23
|
+
|
24
|
+
attrs_from @prior_profile
|
25
|
+
else
|
26
|
+
# This prevents a second inbound transfer from happening:
|
27
|
+
next if seller.transfer_pkey == addr
|
28
|
+
|
29
|
+
# In case they transferred away :
|
30
|
+
@transfer_pkey = seller.transfer_pkey
|
31
|
+
|
32
|
+
attrs_from seller
|
33
|
+
end
|
34
|
+
|
35
|
+
break if @transfer_pkey
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def closed?; (@transfer_pkey == 0); end
|
40
|
+
def active?; @transfer_pkey.nil? end
|
41
|
+
def found?; messages.length > 0; end
|
42
|
+
end
|
43
|
+
|
44
|
+
module ValidateProfile
|
45
|
+
def self.included(base)
|
46
|
+
base.validate :must_have_declaration
|
47
|
+
base.validate :prior_profile_is_valid
|
48
|
+
base.validate :prior_profile_transferred_to_us
|
49
|
+
end
|
50
|
+
|
51
|
+
def must_have_declaration(profile)
|
52
|
+
errors.add :addr, "profile not found" unless profile.messages.length > 0
|
53
|
+
end
|
54
|
+
|
55
|
+
def prior_profile_is_valid(profile)
|
56
|
+
if profile.prior_profile && !profile.prior_profile.valid?
|
57
|
+
errors.add :prior_profile, "invalid"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def prior_profile_transferred_to_us(profile)
|
62
|
+
if profile.prior_profile && profile.prior_profile.transfer_pkey != profile.addr
|
63
|
+
errors.add :prior_profile, "invalid transfer or closed"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# A profile is different than a Seller message, as it's the concatenation of
|
69
|
+
# Seller messages, and is missing the transfer and sender_addr.
|
70
|
+
class SellerProfile < Profile
|
71
|
+
self.message_types = 'SLUPDT'
|
72
|
+
|
73
|
+
state_attr :description, :alias, :communications_pkey
|
74
|
+
|
75
|
+
class Validator < ValidatorBase; include ValidateProfile; end
|
76
|
+
end
|
77
|
+
|
78
|
+
class BuyerProfile < Profile
|
79
|
+
self.message_types = 'BYUPDT'
|
80
|
+
|
81
|
+
state_attr :description, :alias
|
82
|
+
|
83
|
+
class Validator < ValidatorBase; include ValidateProfile; end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Dropzone
|
2
|
+
class ValidatorBase
|
3
|
+
IS_STRING = /\A.+\Z/
|
4
|
+
|
5
|
+
include Veto.validator
|
6
|
+
|
7
|
+
def self.validates_if_present(attr, options)
|
8
|
+
validates attr, options.merge({unless: "self.%s.nil?" % attr.to_s})
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# This lets us set connection parameters across the entire library.
|
13
|
+
# A cattr_inheritable-esque implementation might be worth adding at some point.
|
14
|
+
class RecordBase
|
15
|
+
def blockchain;
|
16
|
+
RecordBase.blockchain
|
17
|
+
end
|
18
|
+
|
19
|
+
def valid?; validator.valid? self; end
|
20
|
+
|
21
|
+
def errors
|
22
|
+
validator.valid? self
|
23
|
+
validator.errors
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def validator; @validator ||= self.class.const_get(:Validator).new; end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
attr_accessor :blockchain
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Dropzone
|
2
|
+
class Seller < MessageBase
|
3
|
+
message_type 'SLUPDT'
|
4
|
+
|
5
|
+
attr_message d: :description, a: :alias
|
6
|
+
attr_message_pkey t: :transfer_pkey, p: :communications_pkey
|
7
|
+
end
|
8
|
+
|
9
|
+
class Seller::Validator < ValidatorBase
|
10
|
+
include MessageValidations
|
11
|
+
include ProfileValidations
|
12
|
+
|
13
|
+
validates :message_type, format: /\ASLUPDT\Z/
|
14
|
+
|
15
|
+
validates_if_present :description, is_string: true
|
16
|
+
validates_if_present :alias, is_string: true
|
17
|
+
|
18
|
+
validates_if_present :communications_pkey, is_pkey: true
|
19
|
+
validates_if_present :transfer_pkey, is_pkey: true
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
module Dropzone
|
2
|
+
class Session
|
3
|
+
CIPHER_ALGORITHM = 'AES-256-CBC'
|
4
|
+
|
5
|
+
class Unauthenticated < StandardError; end
|
6
|
+
class MissingReceiver < StandardError; end
|
7
|
+
class InvalidWithReceiver < StandardError; end
|
8
|
+
class DerAlreadyExists < StandardError; end
|
9
|
+
class InvalidCommunication < StandardError; end
|
10
|
+
class SessionInvalid < StandardError; end
|
11
|
+
|
12
|
+
attr_accessor :priv_key, :receiver_addr, :session_key, :with, :end_block
|
13
|
+
|
14
|
+
def initialize(priv_key, session_secret, options = {})
|
15
|
+
@priv_key, @session_key = priv_key, OpenSSL::BN.new(session_secret, 16)
|
16
|
+
@end_block = options[:end_block] if options.has_key? :end_block
|
17
|
+
|
18
|
+
# Either you attach to an existing session, or create a new one
|
19
|
+
case
|
20
|
+
when options.has_key?(:receiver_addr)
|
21
|
+
# New Session:
|
22
|
+
@receiver_addr = options[:receiver_addr]
|
23
|
+
when options.has_key?(:with)
|
24
|
+
# Existing Session:
|
25
|
+
raise InvalidWithReceiver unless options[:with].receiver_addr == sender_addr
|
26
|
+
@with = options[:with]
|
27
|
+
@receiver_addr = @with.sender_addr
|
28
|
+
else
|
29
|
+
raise MissingReceiver
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def blockchain; self.class.blockchain; end
|
34
|
+
def sender_addr; blockchain.privkey_to_addr priv_key; end
|
35
|
+
|
36
|
+
# Iv passing is supported only for the purpose of making tests completely
|
37
|
+
# deterministic
|
38
|
+
def send(contents, iv = nil)
|
39
|
+
raise Unauthenticated unless authenticated?
|
40
|
+
|
41
|
+
# Cipher Setup:
|
42
|
+
aes = OpenSSL::Cipher::Cipher.new CIPHER_ALGORITHM
|
43
|
+
aes.encrypt
|
44
|
+
|
45
|
+
iv ||= aes.random_iv
|
46
|
+
aes.iv = iv
|
47
|
+
|
48
|
+
aes.key = symm_key
|
49
|
+
|
50
|
+
# Encrypt Time:
|
51
|
+
cipher = aes.update contents
|
52
|
+
cipher << aes.final
|
53
|
+
|
54
|
+
communicate! contents: cipher.to_s, iv: iv
|
55
|
+
end
|
56
|
+
|
57
|
+
alias :<< :send
|
58
|
+
|
59
|
+
def authenticate!(der = nil)
|
60
|
+
is_init = (communication_init.nil? || authenticated?)
|
61
|
+
|
62
|
+
# If we're already authenticated, we'll try to re-initialize. Presumably
|
63
|
+
# one would want to do this if they lost a secret key, or that key were
|
64
|
+
# somehow compromised
|
65
|
+
if is_init
|
66
|
+
dh = OpenSSL::PKey::DH.new(der || 1024)
|
67
|
+
else
|
68
|
+
raise DerAlreadyExists unless der.nil?
|
69
|
+
dh = OpenSSL::PKey::DH.new with.der
|
70
|
+
end
|
71
|
+
|
72
|
+
dh.priv_key = session_key
|
73
|
+
dh.generate_key!
|
74
|
+
|
75
|
+
communicate! session_pkey: [dh.pub_key.to_s(16)].pack('H*'),
|
76
|
+
der: (is_init) ? dh.public_key.to_der : nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def symm_key
|
80
|
+
return @symm_key if @symm_key
|
81
|
+
|
82
|
+
# If we can't compute, then it's ok to merely indicate this:
|
83
|
+
return nil unless communication_init && communication_auth
|
84
|
+
|
85
|
+
dh = OpenSSL::PKey::DH.new communication_init.der
|
86
|
+
dh.priv_key = session_key
|
87
|
+
dh.generate_key!
|
88
|
+
|
89
|
+
@symm_key = dh.compute_key OpenSSL::BN.new(
|
90
|
+
their_pkey.session_pkey.unpack('H*').first, 16)
|
91
|
+
end
|
92
|
+
|
93
|
+
def authenticated?
|
94
|
+
communication_init && communication_auth
|
95
|
+
end
|
96
|
+
|
97
|
+
def communication_init
|
98
|
+
# NOTE that this returns the newest initialization
|
99
|
+
commun_messages.find(&:is_init?)
|
100
|
+
end
|
101
|
+
|
102
|
+
# This is the response to the init
|
103
|
+
def communication_auth
|
104
|
+
# NOTE that this returns the newest auth, or nil if we encounter an init
|
105
|
+
commun_messages.find{|c|
|
106
|
+
break if c.is_init?
|
107
|
+
c.is_auth? }
|
108
|
+
end
|
109
|
+
|
110
|
+
def their_pkey
|
111
|
+
[communication_init, communication_auth].find{|c|
|
112
|
+
c.sender_addr == receiver_addr && c.receiver_addr == sender_addr }
|
113
|
+
end
|
114
|
+
|
115
|
+
def communications
|
116
|
+
if authenticated?
|
117
|
+
communications = commun_messages(
|
118
|
+
start_block: communication_init.block_height ).reject(&:is_auth?)
|
119
|
+
communications.each{|c| c.symm_key = symm_key}
|
120
|
+
communications
|
121
|
+
else
|
122
|
+
[]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
# Addr's of who this conversation is between
|
129
|
+
def between
|
130
|
+
[sender_addr, receiver_addr]
|
131
|
+
end
|
132
|
+
|
133
|
+
def commun_messages(options = {})
|
134
|
+
options[:type] = 'COMMUN'
|
135
|
+
options[:end_block] = @end_block if @end_block
|
136
|
+
options[:between] = between
|
137
|
+
blockchain.messages_by_addr(sender_addr, options)
|
138
|
+
end
|
139
|
+
|
140
|
+
def communicate!(attrs)
|
141
|
+
comm = Communication.new( {receiver_addr: receiver_addr,
|
142
|
+
sender_addr: sender_addr}.merge(attrs) )
|
143
|
+
|
144
|
+
raise InvalidCommunication unless comm.valid?
|
145
|
+
|
146
|
+
comm.save! priv_key
|
147
|
+
end
|
148
|
+
|
149
|
+
class << self
|
150
|
+
attr_writer :blockchain
|
151
|
+
|
152
|
+
def blockchain
|
153
|
+
@blockchain || Dropzone::RecordBase.blockchain
|
154
|
+
end
|
155
|
+
|
156
|
+
def all(addr)
|
157
|
+
blockchain.messages_by_addr(addr, type: 'COMMUN').find_all(&:is_init?)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|