Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

_seller.py 33KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. ###############################################################################
  2. #
  3. # The MIT License (MIT)
  4. #
  5. # Copyright (c) typedef int GmbH
  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
  15. # all 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
  23. # THE SOFTWARE.
  24. #
  25. ###############################################################################
  26. import asyncio
  27. import binascii
  28. import os
  29. import uuid
  30. from autobahn.wamp.types import RegisterOptions, CallDetails
  31. from autobahn.wamp.exception import ApplicationError, TransportLost
  32. from autobahn.wamp.protocol import ApplicationSession
  33. from ._util import unpack_uint256, pack_uint256
  34. from txaio import time_ns
  35. import cbor2
  36. import eth_keys
  37. import nacl.secret
  38. import nacl.utils
  39. import nacl.public
  40. import txaio
  41. from ..util import hl, hlval
  42. from ._eip712_channel_close import sign_eip712_channel_close, recover_eip712_channel_close
  43. class KeySeries(object):
  44. """
  45. Data encryption key series with automatic (time-based) key rotation
  46. and key offering (to the XBR market maker).
  47. """
  48. def __init__(self, api_id, price, interval=None, count=None, on_rotate=None):
  49. """
  50. :param api_id: ID of the API for which to generate keys.
  51. :type api_id: bytes
  52. :param price: Price per key in key series.
  53. :type price: int
  54. :param interval: Interval in seconds after which to auto-rotate key.
  55. :type interval: int
  56. :param count: Number of encryption operations after which to auto-rotate key.
  57. :type count: int
  58. :param on_rotate: Optional user callback fired after key was rotated.
  59. :type on_rotate: callable
  60. """
  61. assert type(api_id) == bytes and len(api_id) == 16
  62. assert type(price) == int and price >= 0
  63. assert interval is None or (type(interval) == int and interval > 0)
  64. assert count is None or (type(count) == int and count > 0)
  65. assert (interval is None and count is not None) or (interval is not None and count is None)
  66. assert on_rotate is None or callable(on_rotate)
  67. self._api_id = api_id
  68. self._price = price
  69. self._interval = interval
  70. self._count = count
  71. self._count_current = 0
  72. self._on_rotate = on_rotate
  73. self._id = None
  74. self._key = None
  75. self._box = None
  76. self._archive = {}
  77. @property
  78. def key_id(self):
  79. """
  80. Get current XBR data encryption key ID (of the keys being rotated
  81. in a series).
  82. :return: Current key ID in key series (16 bytes).
  83. :rtype: bytes
  84. """
  85. return self._id
  86. async def encrypt(self, payload):
  87. """
  88. Encrypt data with the current XBR data encryption key.
  89. :param payload: Application payload to encrypt.
  90. :type payload: object
  91. :return: The ciphertext for the encrypted application payload.
  92. :rtype: bytes
  93. """
  94. data = cbor2.dumps(payload)
  95. if self._count is not None:
  96. self._count_current += 1
  97. if self._count_current >= self._count:
  98. await self._rotate()
  99. self._count_current = 0
  100. ciphertext = self._box.encrypt(data)
  101. return self._id, 'cbor', ciphertext
  102. def encrypt_key(self, key_id, buyer_pubkey):
  103. """
  104. Encrypt a (previously used) XBR data encryption key with a buyer public key.
  105. :param key_id: ID of the data encryption key to encrypt.
  106. :type key_id: bytes
  107. :param buyer_pubkey: Buyer WAMP public key (Ed25519) to asymmetrically encrypt
  108. the data encryption key (selected by ``key_id``) against.
  109. :type buyer_pubkey: bytes
  110. :return: The ciphertext for the encrypted data encryption key.
  111. :rtype: bytes
  112. """
  113. assert type(key_id) == bytes and len(key_id) == 16
  114. assert type(buyer_pubkey) == bytes and len(buyer_pubkey) == 32
  115. key, _ = self._archive[key_id]
  116. sendkey_box = nacl.public.SealedBox(nacl.public.PublicKey(buyer_pubkey,
  117. encoder=nacl.encoding.RawEncoder))
  118. encrypted_key = sendkey_box.encrypt(key, encoder=nacl.encoding.RawEncoder)
  119. return encrypted_key
  120. def start(self):
  121. raise NotImplementedError()
  122. def stop(self):
  123. raise NotImplementedError()
  124. async def _rotate(self):
  125. # generate new ID for next key in key series
  126. self._id = os.urandom(16)
  127. # generate next data encryption key in key series
  128. self._key = nacl.utils.random(nacl.secret.SecretBox.KEY_SIZE)
  129. # create secretbox from new key
  130. self._box = nacl.secret.SecretBox(self._key)
  131. # add key to archive
  132. self._archive[self._id] = (self._key, self._box)
  133. self.log.debug(
  134. '{tx_type} key "{key_id}" rotated [api_id="{api_id}"]',
  135. tx_type=hl('XBR ROTATE', color='magenta'),
  136. key_id=hl(uuid.UUID(bytes=self._id)),
  137. api_id=hl(uuid.UUID(bytes=self._api_id)))
  138. # maybe fire user callback
  139. if self._on_rotate:
  140. await self._on_rotate(self)
  141. class PayingChannel(object):
  142. def __init__(self, adr, seq, balance):
  143. assert type(adr) == bytes and len(adr) == 16
  144. assert type(seq) == int and seq >= 0
  145. assert type(balance) == int and balance >= 0
  146. self._adr = adr
  147. self._seq = seq
  148. self._balance = balance
  149. class SimpleSeller(object):
  150. log = None
  151. KeySeries = None
  152. STATE_NONE = 0
  153. STATE_STARTING = 1
  154. STATE_STARTED = 2
  155. STATE_STOPPING = 3
  156. STATE_STOPPED = 4
  157. def __init__(self, market_maker_adr, seller_key, provider_id=None):
  158. """
  159. :param market_maker_adr: Market maker public Ethereum address (20 bytes).
  160. :type market_maker_adr: bytes
  161. :param seller_key: Seller (delegate) private Ethereum key (32 bytes).
  162. :type seller_key: bytes
  163. :param provider_id: Optional explicit data provider ID. When not given, the seller delegate
  164. public WAMP key (Ed25519 in Hex) is used as the provider ID. This must be a valid WAMP URI part.
  165. :type provider_id: string
  166. """
  167. assert type(market_maker_adr) == bytes and len(market_maker_adr) == 20, 'market_maker_adr must be bytes[20], but got "{}"'.format(market_maker_adr)
  168. assert type(seller_key) == bytes and len(seller_key) == 32, 'seller delegate must be bytes[32], but got "{}"'.format(seller_key)
  169. assert provider_id is None or type(provider_id) == str, 'provider_id must be None or string, but got "{}"'.format(provider_id)
  170. self.log = txaio.make_logger()
  171. # current seller state
  172. self._state = SimpleSeller.STATE_NONE
  173. # market maker address
  174. self._market_maker_adr = market_maker_adr
  175. self._xbrmm_config = None
  176. # seller raw ethereum private key (32 bytes)
  177. self._pkey_raw = seller_key
  178. # seller ethereum private key object
  179. self._pkey = eth_keys.keys.PrivateKey(seller_key)
  180. # seller ethereum private account from raw private key
  181. # FIXME
  182. # self._acct = Account.privateKeyToAccount(self._pkey)
  183. self._acct = None
  184. # seller ethereum account canonical address
  185. self._addr = self._pkey.public_key.to_canonical_address()
  186. # seller ethereum account canonical checksummed address
  187. # FIXME
  188. # self._caddr = web3.Web3.toChecksumAddress(self._addr)
  189. self._caddr = None
  190. # seller provider ID
  191. self._provider_id = provider_id or str(self._pkey.public_key)
  192. self._channels = {}
  193. # will be filled with on-chain payment channel contract, once started
  194. self._channel = None
  195. # channel current (off-chain) balance
  196. self._balance = 0
  197. # channel sequence number
  198. self._seq = 0
  199. self._keys = {}
  200. self._keys_map = {}
  201. # after start() is running, these will be set
  202. self._session = None
  203. self._session_regs = None
  204. @property
  205. def public_key(self):
  206. """
  207. This seller delegate public Ethereum key.
  208. :return: Ethereum public key of this seller delegate.
  209. :rtype: bytes
  210. """
  211. return self._pkey.public_key
  212. def add(self, api_id, prefix, price, interval=None, count=None, categories=None):
  213. """
  214. Add a new (rotating) private encryption key for encrypting data on the given API.
  215. :param api_id: API for which to create a new series of rotating encryption keys.
  216. :type api_id: bytes
  217. :param price: Price in XBR token per key.
  218. :type price: int
  219. :param interval: Interval (in seconds) after which to auto-rotate the encryption key.
  220. :type interval: int
  221. :param count: Number of encryption operations after which to auto-rotate the encryption key.
  222. :type count: int
  223. """
  224. assert type(api_id) == bytes and len(api_id) == 16 and api_id not in self._keys
  225. assert type(price) == int and price >= 0
  226. assert interval is None or (type(interval) == int and interval > 0)
  227. assert count is None or (type(count) == int and count > 0)
  228. assert (interval is None and count is not None) or (interval is not None and count is None)
  229. assert categories is None or (type(categories) == dict and (type(k) == str for k in categories.keys()) and (type(v) == str for v in categories.values())), 'invalid categories type (must be dict) or category key or value type (must both be string)'
  230. async def on_rotate(key_series):
  231. key_id = key_series.key_id
  232. self._keys_map[key_id] = key_series
  233. # FIXME: expose the knobs hard-coded in below ..
  234. # offer the key to the market maker (retry 5x in specific error cases)
  235. retries = 5
  236. while retries:
  237. try:
  238. valid_from = time_ns() - 10 * 10 ** 9
  239. delegate = self._addr
  240. # FIXME: sign the supplied offer information using self._pkey
  241. signature = os.urandom(65)
  242. provider_id = self._provider_id
  243. offer = await self._session.call('xbr.marketmaker.place_offer',
  244. key_id,
  245. api_id,
  246. prefix,
  247. valid_from,
  248. delegate,
  249. signature,
  250. privkey=None,
  251. price=pack_uint256(price) if price is not None else None,
  252. categories=categories,
  253. expires=None,
  254. copies=None,
  255. provider_id=provider_id)
  256. self.log.debug(
  257. '{tx_type} key "{key_id}" offered for {price} [api_id={api_id}, prefix="{prefix}", delegate="{delegate}"]',
  258. tx_type=hl('XBR OFFER ', color='magenta'),
  259. key_id=hl(uuid.UUID(bytes=key_id)),
  260. api_id=hl(uuid.UUID(bytes=api_id)),
  261. price=hl(str(int(price / 10 ** 18) if price is not None else 0) + ' XBR', color='magenta'),
  262. delegate=hl(binascii.b2a_hex(delegate).decode()),
  263. prefix=hl(prefix))
  264. self.log.debug('offer={offer}', offer=offer)
  265. break
  266. except ApplicationError as e:
  267. if e.error == 'wamp.error.no_such_procedure':
  268. self.log.warn('xbr.marketmaker.offer: procedure unavailable!')
  269. else:
  270. self.log.failure()
  271. break
  272. except TransportLost:
  273. self.log.warn('TransportLost while calling xbr.marketmaker.offer!')
  274. break
  275. except:
  276. self.log.failure()
  277. retries -= 1
  278. self.log.warn('Failed to place offer for key! Retrying {retries}/5 ..', retries=retries)
  279. await asyncio.sleep(1)
  280. key_series = self.KeySeries(api_id, price, interval=interval, count=count, on_rotate=on_rotate)
  281. self._keys[api_id] = key_series
  282. self.log.debug('Created new key series {key_series}', key_series=key_series)
  283. return key_series
  284. async def start(self, session):
  285. """
  286. Start rotating keys and placing key offers with the XBR market maker.
  287. :param session: WAMP session over which to communicate with the XBR market maker.
  288. :type session: :class:`autobahn.wamp.protocol.ApplicationSession`
  289. """
  290. assert isinstance(session, ApplicationSession), 'session must be an ApplicationSession, was "{}"'.format(session)
  291. assert self._state in [SimpleSeller.STATE_NONE, SimpleSeller.STATE_STOPPED], 'seller already running'
  292. self._state = SimpleSeller.STATE_STARTING
  293. self._session = session
  294. self._session_regs = []
  295. self.log.debug('Start selling from seller delegate address {address} (public key 0x{public_key}..)',
  296. address=hl(self._caddr),
  297. public_key=binascii.b2a_hex(self._pkey.public_key[:10]).decode())
  298. # get the currently active (if any) paying channel for the delegate
  299. self._channel = await session.call('xbr.marketmaker.get_active_paying_channel', self._addr)
  300. if not self._channel:
  301. raise Exception('no active paying channel found')
  302. channel_oid = self._channel['channel_oid']
  303. assert type(channel_oid) == bytes and len(channel_oid) == 16
  304. self._channel_oid = uuid.UUID(bytes=channel_oid)
  305. procedure = 'xbr.provider.{}.sell'.format(self._provider_id)
  306. reg = await session.register(self.sell, procedure, options=RegisterOptions(details_arg='details'))
  307. self._session_regs.append(reg)
  308. self.log.debug('Registered procedure "{procedure}"', procedure=hl(reg.procedure))
  309. procedure = 'xbr.provider.{}.close_channel'.format(self._provider_id)
  310. reg = await session.register(self.close_channel, procedure, options=RegisterOptions(details_arg='details'))
  311. self._session_regs.append(reg)
  312. self.log.debug('Registered procedure "{procedure}"', procedure=hl(reg.procedure))
  313. for key_series in self._keys.values():
  314. await key_series.start()
  315. self._xbrmm_config = await session.call('xbr.marketmaker.get_config')
  316. # get the current (off-chain) balance of the paying channel
  317. paying_balance = await session.call('xbr.marketmaker.get_paying_channel_balance', self._channel_oid.bytes)
  318. # FIXME
  319. if type(paying_balance['remaining']) == bytes:
  320. paying_balance['remaining'] = unpack_uint256(paying_balance['remaining'])
  321. if not paying_balance['remaining'] > 0:
  322. raise Exception('no off-chain balance remaining on paying channel')
  323. self._channels[channel_oid] = PayingChannel(channel_oid, paying_balance['seq'], paying_balance['remaining'])
  324. self._state = SimpleSeller.STATE_STARTED
  325. # FIXME
  326. self._balance = paying_balance['remaining']
  327. if type(self._balance) == bytes:
  328. self._balance = unpack_uint256(self._balance)
  329. self._seq = paying_balance['seq']
  330. self.log.info('Ok, seller delegate started [active paying channel {channel_oid} with remaining balance {remaining} at sequence {seq}]',
  331. channel_oid=hl(self._channel_oid), remaining=hlval(self._balance), seq=hlval(self._seq))
  332. return paying_balance['remaining']
  333. async def stop(self):
  334. """
  335. Stop rotating/offering keys to the XBR market maker.
  336. """
  337. assert self._state in [SimpleSeller.STATE_STARTED], 'seller not running'
  338. self._state = SimpleSeller.STATE_STOPPING
  339. dl = []
  340. for key_series in self._keys.values():
  341. d = key_series.stop()
  342. dl.append(d)
  343. if self._session_regs:
  344. if self._session and self._session.is_attached():
  345. # voluntarily unregister interface
  346. for reg in self._session_regs:
  347. d = reg.unregister()
  348. dl.append(d)
  349. self._session_regs = None
  350. d = txaio.gather(dl)
  351. try:
  352. await d
  353. except:
  354. self.log.failure()
  355. finally:
  356. self._state = SimpleSeller.STATE_STOPPED
  357. self._session = None
  358. self.log.info('Ok, seller delegate stopped.')
  359. async def balance(self):
  360. """
  361. Return current (off-chain) balance of paying channel:
  362. * ``amount``: The initial amount with which the paying channel was opened.
  363. * ``remaining``: The remaining amount of XBR in the paying channel that can be earned.
  364. * ``inflight``: The amount of XBR allocated to sell transactions that are currently processed.
  365. :return: Current paying balance.
  366. :rtype: dict
  367. """
  368. if self._state not in [SimpleSeller.STATE_STARTED]:
  369. raise RuntimeError('seller not running')
  370. if not self._session or not self._session.is_attached():
  371. raise RuntimeError('market-maker session not attached')
  372. paying_balance = await self._session.call('xbr.marketmaker.get_paying_channel_balance', self._channel['channel_oid'])
  373. return paying_balance
  374. async def wrap(self, api_id, uri, payload):
  375. """
  376. Encrypt and wrap application payload for a given API and destined for a specific WAMP URI.
  377. :param api_id: API for which to encrypt and wrap the application payload for.
  378. :type api_id: bytes
  379. :param uri: WAMP URI the application payload is destined for (eg the procedure or topic URI).
  380. :type uri: str
  381. :param payload: Application payload to encrypt and wrap.
  382. :type payload: object
  383. :return: The encrypted and wrapped application payload: a tuple with ``(key_id, serializer, ciphertext)``.
  384. :rtype: tuple
  385. """
  386. assert type(api_id) == bytes and len(api_id) == 16 and api_id in self._keys
  387. assert type(uri) == str
  388. assert payload is not None
  389. keyseries = self._keys[api_id]
  390. key_id, serializer, ciphertext = await keyseries.encrypt(payload)
  391. return key_id, serializer, ciphertext
  392. def close_channel(self, market_maker_adr, channel_oid, channel_seq, channel_balance, channel_is_final,
  393. marketmaker_signature, details=None):
  394. """
  395. Called by a XBR Market Maker to close a paying channel.
  396. """
  397. assert type(market_maker_adr) == bytes and len(market_maker_adr) == 20, 'market_maker_adr must be bytes[20], but was {}'.format(type(market_maker_adr))
  398. assert type(channel_oid) == bytes and len(channel_oid) == 16, 'channel_oid must be bytes[16], but was {}'.format(type(channel_oid))
  399. assert type(channel_seq) == int, 'channel_seq must be int, but was {}'.format(type(channel_seq))
  400. assert type(channel_balance) == bytes and len(channel_balance) == 32, 'channel_balance must be bytes[32], but was {}'.format(type(channel_balance))
  401. assert type(channel_is_final) == bool, 'channel_is_final must be bool, but was {}'.format(type(channel_is_final))
  402. assert type(marketmaker_signature) == bytes and len(marketmaker_signature) == (32 + 32 + 1), 'marketmaker_signature must be bytes[65], but was {}'.format(type(marketmaker_signature))
  403. assert details is None or isinstance(details, CallDetails), 'details must be autobahn.wamp.types.CallDetails'
  404. # check that the delegate_adr fits what we expect for the market maker
  405. if market_maker_adr != self._market_maker_adr:
  406. raise ApplicationError('xbr.error.unexpected_delegate_adr',
  407. '{}.sell() - unexpected market maker (delegate) address: expected 0x{}, but got 0x{}'.format(self.__class__.__name__, binascii.b2a_hex(self._market_maker_adr).decode(), binascii.b2a_hex(market_maker_adr).decode()))
  408. # FIXME: must be the currently active channel .. and we need to track all of these
  409. if channel_oid != self._channel['channel_oid']:
  410. self._session.leave()
  411. raise ApplicationError('xbr.error.unexpected_channel_oid',
  412. '{}.sell() - unexpected paying channel address: expected 0x{}, but got 0x{}'.format(self.__class__.__name__, binascii.b2a_hex(self._channel['channel_oid']).decode(), binascii.b2a_hex(channel_oid).decode()))
  413. # channel sequence number: check we have consensus on off-chain channel state with peer (which is the market maker)
  414. if channel_seq != self._seq:
  415. raise ApplicationError('xbr.error.unexpected_channel_seq',
  416. '{}.sell() - unexpected channel (after tx) sequence number: expected {}, but got {}'.format(self.__class__.__name__, self._seq + 1, channel_seq))
  417. # channel balance: check we have consensus on off-chain channel state with peer (which is the market maker)
  418. channel_balance = unpack_uint256(channel_balance)
  419. if channel_balance != self._balance:
  420. raise ApplicationError('xbr.error.unexpected_channel_balance',
  421. '{}.sell() - unexpected channel (after tx) balance: expected {}, but got {}'.format(self.__class__.__name__, self._balance, channel_balance))
  422. # XBRSIG: check the signature (over all input data for the buying of the key)
  423. signer_address = recover_eip712_channel_close(channel_oid, channel_seq, channel_balance, channel_is_final, marketmaker_signature)
  424. if signer_address != market_maker_adr:
  425. self.log.warn('{klass}.sell()::XBRSIG[4/8] - EIP712 signature invalid: signer_address={signer_address}, delegate_adr={delegate_adr}',
  426. klass=self.__class__.__name__,
  427. signer_address=hl(binascii.b2a_hex(signer_address).decode()),
  428. delegate_adr=hl(binascii.b2a_hex(market_maker_adr).decode()))
  429. raise ApplicationError('xbr.error.invalid_signature', '{}.sell()::XBRSIG[4/8] - EIP712 signature invalid or not signed by market maker'.format(self.__class__.__name__))
  430. # XBRSIG: compute EIP712 typed data signature
  431. seller_signature = sign_eip712_channel_close(self._pkey_raw, channel_oid, channel_seq, channel_balance, channel_is_final)
  432. receipt = {
  433. 'delegate': self._addr,
  434. 'seq': channel_seq,
  435. 'balance': pack_uint256(channel_balance),
  436. 'is_final': channel_is_final,
  437. 'signature': seller_signature,
  438. }
  439. self.log.debug('{klass}.close_channel() - {tx_type} closing channel {channel_oid}, closing balance {channel_balance}, closing sequence {channel_seq} [caller={caller}, caller_authid="{caller_authid}"]',
  440. klass=self.__class__.__name__,
  441. tx_type=hl('XBR CLOSE ', color='magenta'),
  442. channel_balance=hl(str(int(channel_balance / 10 ** 18)) + ' XBR', color='magenta'),
  443. channel_seq=hl(channel_seq),
  444. channel_oid=hl(binascii.b2a_hex(channel_oid).decode()),
  445. caller=hl(details.caller),
  446. caller_authid=hl(details.caller_authid))
  447. return receipt
  448. def sell(self, market_maker_adr, buyer_pubkey, key_id, channel_oid, channel_seq, amount, balance, signature, details=None):
  449. """
  450. Called by a XBR Market Maker to buy a data encyption key. The XBR Market Maker here is
  451. acting for (triggered by) the XBR buyer delegate.
  452. :param market_maker_adr: The market maker Ethereum address. The technical buyer is usually the
  453. XBR market maker (== the XBR delegate of the XBR market operator).
  454. :type market_maker_adr: bytes of length 20
  455. :param buyer_pubkey: The buyer delegate Ed25519 public key.
  456. :type buyer_pubkey: bytes of length 32
  457. :param key_id: The UUID of the data encryption key to buy.
  458. :type key_id: bytes of length 16
  459. :param channel_oid: The on-chain channel contract address.
  460. :type channel_oid: bytes of length 16
  461. :param channel_seq: Paying channel sequence off-chain transaction number.
  462. :type channel_seq: int
  463. :param amount: The amount paid by the XBR Buyer via the XBR Market Maker.
  464. :type amount: bytes
  465. :param balance: Balance remaining in the payment channel (from the market maker to the
  466. seller) after successfully buying the key.
  467. :type balance: bytes
  468. :param signature: Signature over the supplied buying information, using the Ethereum
  469. private key of the market maker (which is the delegate of the marker operator).
  470. :type signature: bytes of length 65
  471. :param details: Caller details. The call will come from the XBR Market Maker.
  472. :type details: :class:`autobahn.wamp.types.CallDetails`
  473. :return: The data encryption key, itself encrypted to the public key of the original buyer.
  474. :rtype: bytes
  475. """
  476. assert type(market_maker_adr) == bytes and len(market_maker_adr) == 20, 'delegate_adr must be bytes[20]'
  477. assert type(buyer_pubkey) == bytes and len(buyer_pubkey) == 32, 'buyer_pubkey must be bytes[32]'
  478. assert type(key_id) == bytes and len(key_id) == 16, 'key_id must be bytes[16]'
  479. assert type(channel_oid) == bytes and len(channel_oid) == 16, 'channel_oid must be bytes[16]'
  480. assert type(channel_seq) == int, 'channel_seq must be int'
  481. assert type(amount) == bytes and len(amount) == 32, 'amount_paid must be bytes[32], but was {}'.format(type(amount))
  482. assert type(balance) == bytes and len(amount) == 32, 'post_balance must be bytes[32], but was {}'.format(type(balance))
  483. assert type(signature) == bytes and len(signature) == (32 + 32 + 1), 'signature must be bytes[65]'
  484. assert details is None or isinstance(details, CallDetails), 'details must be autobahn.wamp.types.CallDetails'
  485. amount = unpack_uint256(amount)
  486. balance = unpack_uint256(balance)
  487. # check that the delegate_adr fits what we expect for the market maker
  488. if market_maker_adr != self._market_maker_adr:
  489. raise ApplicationError('xbr.error.unexpected_marketmaker_adr',
  490. '{}.sell() - unexpected market maker address: expected 0x{}, but got 0x{}'.format(self.__class__.__name__, binascii.b2a_hex(self._market_maker_adr).decode(), binascii.b2a_hex(market_maker_adr).decode()))
  491. # get the key series given the key_id
  492. if key_id not in self._keys_map:
  493. raise ApplicationError('crossbar.error.no_such_object', '{}.sell() - no key with ID "{}"'.format(self.__class__.__name__, key_id))
  494. key_series = self._keys_map[key_id]
  495. # FIXME: must be the currently active channel .. and we need to track all of these
  496. if channel_oid != self._channel['channel_oid']:
  497. self._session.leave()
  498. raise ApplicationError('xbr.error.unexpected_channel_oid',
  499. '{}.sell() - unexpected paying channel address: expected 0x{}, but got 0x{}'.format(self.__class__.__name__, binascii.b2a_hex(self._channel['channel_oid']).decode(), binascii.b2a_hex(channel_oid).decode()))
  500. # channel sequence number: check we have consensus on off-chain channel state with peer (which is the market maker)
  501. if channel_seq != self._seq + 1:
  502. raise ApplicationError('xbr.error.unexpected_channel_seq',
  503. '{}.sell() - unexpected channel (after tx) sequence number: expected {}, but got {}'.format(self.__class__.__name__, self._seq + 1, channel_seq))
  504. # channel balance: check we have consensus on off-chain channel state with peer (which is the market maker)
  505. if balance != self._balance - amount:
  506. raise ApplicationError('xbr.error.unexpected_channel_balance',
  507. '{}.sell() - unexpected channel (after tx) balance: expected {}, but got {}'.format(self.__class__.__name__, self._balance - amount, balance))
  508. # FIXME
  509. current_block_number = 1
  510. verifying_chain_id = self._xbrmm_config['verifying_chain_id']
  511. verifying_contract_adr = binascii.a2b_hex(self._xbrmm_config['verifying_contract_adr'][2:])
  512. market_oid = self._channel['market_oid']
  513. # XBRSIG[4/8]: check the signature (over all input data for the buying of the key)
  514. signer_address = recover_eip712_channel_close(verifying_chain_id, verifying_contract_adr, current_block_number,
  515. market_oid, channel_oid, channel_seq, balance, False, signature)
  516. if signer_address != market_maker_adr:
  517. self.log.warn('{klass}.sell()::XBRSIG[4/8] - EIP712 signature invalid: signer_address={signer_address}, delegate_adr={delegate_adr}',
  518. klass=self.__class__.__name__,
  519. signer_address=hl(binascii.b2a_hex(signer_address).decode()),
  520. delegate_adr=hl(binascii.b2a_hex(market_maker_adr).decode()))
  521. raise ApplicationError('xbr.error.invalid_signature', '{}.sell()::XBRSIG[4/8] - EIP712 signature invalid or not signed by market maker'.format(self.__class__.__name__))
  522. # now actually update our local knowledge of the channel state
  523. # FIXME: what if code down below fails?
  524. self._seq += 1
  525. self._balance -= amount
  526. # encrypt the data encryption key against the original buyer delegate Ed25519 public key
  527. sealed_key = key_series.encrypt_key(key_id, buyer_pubkey)
  528. assert type(sealed_key) == bytes and len(sealed_key) == 80, '{}.sell() - unexpected sealed key computed (expected bytes[80]): {}'.format(self.__class__.__name__, sealed_key)
  529. # XBRSIG[5/8]: compute EIP712 typed data signature
  530. seller_signature = sign_eip712_channel_close(self._pkey_raw, verifying_chain_id, verifying_contract_adr,
  531. current_block_number, market_oid, channel_oid, self._seq,
  532. self._balance, False)
  533. receipt = {
  534. # key ID that has been bought
  535. 'key_id': key_id,
  536. # seller delegate address that sold the key
  537. 'delegate': self._addr,
  538. # buyer delegate Ed25519 public key with which the bought key was sealed
  539. 'buyer_pubkey': buyer_pubkey,
  540. # finally return what the consumer (buyer) was actually interested in:
  541. # the data encryption key, sealed (public key Ed25519 encrypted) to the
  542. # public key of the buyer delegate
  543. 'sealed_key': sealed_key,
  544. # paying channel off-chain transaction sequence numbers
  545. 'channel_seq': self._seq,
  546. # amount paid for the key
  547. 'amount': amount,
  548. # paying channel amount remaining
  549. 'balance': self._balance,
  550. # seller (delegate) signature
  551. 'signature': seller_signature,
  552. }
  553. self.log.info('{klass}.sell() - {tx_type} key "{key_id}" sold for {amount_earned} - balance is {balance} [caller={caller}, caller_authid="{caller_authid}", buyer_pubkey="{buyer_pubkey}"]',
  554. klass=self.__class__.__name__,
  555. tx_type=hl('XBR SELL ', color='magenta'),
  556. key_id=hl(uuid.UUID(bytes=key_id)),
  557. amount_earned=hl(str(int(amount / 10 ** 18)) + ' XBR', color='magenta'),
  558. balance=hl(str(int(self._balance / 10 ** 18)) + ' XBR', color='magenta'),
  559. # paying_channel=hl(binascii.b2a_hex(paying_channel).decode()),
  560. caller=hl(details.caller),
  561. caller_authid=hl(details.caller_authid),
  562. buyer_pubkey=hl(binascii.b2a_hex(buyer_pubkey).decode()))
  563. return receipt