Advertisement
chrisrico

Armory offline serial server

Dec 11th, 2012
212
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Diff 56.29 KB | None | 0 0
  1. Index: ArmoryQt.py
  2. IDEA additional info:
  3. Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
  4. <+>UTF-8
  5. ===================================================================
  6. --- ArmoryQt.py (revision e1b435821967518f46f5d1722bf6245e5e011e29)
  7. +++ ArmoryQt.py (revision )
  8. @@ -32,6 +32,7 @@
  9.  import platform
  10.  import traceback
  11.  import socket
  12. +import zlib
  13.  from datetime import datetime
  14.  
  15.  # PyQt4 Imports
  16. @@ -122,14 +123,23 @@
  17.        self.setupSystemTray()
  18.        self.setupUriRegistration()
  19.  
  20. +      # Setup serial connection
  21. +      self.setupSerialConnection()
  22.  
  23.        self.extraHeartbeatOnline = []
  24.        self.extraHeartbeatAlways = []
  25.  
  26. -      self.lblArmoryStatus = QRichLabel('<font color=%s><i>Disconnected</i></font>' % \
  27. +      self.lblArmoryStatus = QRichLabel('<font color=%s><i>Bitcoin Disconnected</i></font>' % \
  28.                                             htmlColor('TextWarn'), doWrap=False)
  29. +      self.lblArmoryStatus.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
  30.        self.statusBar().insertPermanentWidget(0, self.lblArmoryStatus)
  31.  
  32. +
  33. +      self.lblSerialStatus = QRichLabel('<font color=%s><i>Serial Disconnected</i></font>' % \
  34. +                                           htmlColor('TextWarn'), doWrap=False)
  35. +      self.lblSerialStatus.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
  36. +      self.statusBar().insertPermanentWidget(1, self.lblSerialStatus)
  37. +
  38.        # Keep a persistent printer object for paper backups
  39.        self.printer = QPrinter(QPrinter.HighResolution)
  40.        self.printer.setPageSize(QPrinter.Letter)
  41. @@ -865,10 +875,36 @@
  42.                 SetValueEx(registryKey, name, 0, REG_SZ, val)
  43.                 CloseKey(registryKey)
  44.  
  45. +   class SerialMessageEmitter(QObject):
  46. +      messageSignal = pyqtSignal(object)
  47. -        
  48. +
  49. +      def __init__(self, parent):
  50. +         QObject.__init__(self, parent)
  51. -        
  52. +
  53. +      def messageReceived(self, message):
  54. +         self.messageSignal.emit(message)
  55.  
  56. +      def register(self, receiver, method):
  57. +         self.messageSignal.connect(method)
  58.  
  59. +      def unregister(self, receiver, method):
  60. +         self.messageSignal.disconnect(method)
  61. +
  62. +   def serialConnectionChanged(self, message):
  63. +      if isinstance(message, bool):
  64. +         if message:
  65. +            self.lblSerialStatus.setText('<font color=%s>Serial Connected</font>' % htmlColor('TextGreen'))
  66. +         else:
  67. +            self.lblSerialStatus.setText('<font color=%s><i>Serial Disconnected</i></font>' % htmlColor('TextWarn'))
  68. +
  69. +   def setupSerialConnection(self):
  70. +      device = self.getSettingOrSetDefault('Serial_Device', '/dev/ttyUSB0')
  71. +      rate = self.getSettingOrSetDefault('Serial_Rate', 115200)
  72. +      self.serialEmitter = self.SerialMessageEmitter(self)
  73. +      self.serialEmitter.register(self, self.serialConnectionChanged)
  74. +      self.serialConnection = PySerialConnection(device, rate, self.serialEmitter.messageReceived)
  75. +      self.serialConnection.open()
  76. +
  77.     #############################################################################
  78.     def execOfflineTx(self):
  79.        dlgSelect = DlgOfflineSelect(self, self)
  80. @@ -1219,7 +1255,7 @@
  81.              self.netMode = NETWORKMODE.Disconnected
  82.              self.setDashboardDetails()
  83.              self.lblArmoryStatus.setText( \
  84. -               '<font color=%s><i>Disconnected</i></font>' % htmlColor('TextWarn'))
  85. +               '<font color=%s><i>Bitcoin Disconnected</i></font>' % htmlColor('TextWarn'))
  86.              if not self.getSettingOrSetDefault('NotifyDiscon', True):
  87.                 return
  88.    
  89. @@ -1237,7 +1273,7 @@
  90.              self.netMode = NETWORKMODE.Full
  91.              self.setDashboardDetails()
  92.              self.lblArmoryStatus.setText(\
  93. -                     '<font color=%s>Connected (%s blocks)</font> ' %
  94. +                     '<font color=%s>Bitcoin Connected (%s blocks)</font> ' %
  95.                       (htmlColor('TextGreen'), self.currBlockNum))
  96.              if not self.getSettingOrSetDefault('NotifyReconn', True):
  97.                 return
  98. @@ -1631,7 +1667,7 @@
  99.           self.statusBar().showMessage('Blockchain loaded, wallets sync\'d!', 10000)
  100.           if self.netMode==NETWORKMODE.Full:
  101.              self.lblArmoryStatus.setText(\
  102. -               '<font color=%s>Connected (%s blocks)</font> ' %
  103. +               '<font color=%s>Bitcoin Connected (%s blocks)</font> ' %
  104.                 (htmlColor('TextGreen'), self.currBlockNum))
  105.           self.blkReceived  = self.getSettingOrSetDefault('LastBlkRecvTime', 0)
  106.  
  107. @@ -2431,6 +2467,8 @@
  108.              self.execGetImportWltName()
  109.           elif dlg.importType_paper:
  110.              self.execRestorePaperBackup()
  111. +         elif dlg.importType_serial:
  112. +            self.execImportSerial()
  113.           elif dlg.importType_migrate:
  114.              self.execMigrateSatoshi()
  115.  
  116. @@ -2580,7 +2618,106 @@
  117.           self.addWalletToApplication(dlgPaper.newWallet, walletIsNew=False)
  118.           #self.newWalletList.append([dlgPaper.newWallet, False])
  119.           LOGINFO('Import Complete!')
  120. -  
  121. +
  122. +   def execImportSerial(self):
  123. +      self.serialEmitter.register(self, self.receiveOnlineWalletList)
  124. +      self.serialConnection.write(pb.OnlineWalletRequest())
  125. +
  126. +   def receiveOnlineWalletList(self, response):
  127. +      if isinstance(response, pb.OnlineWalletResponse):
  128. +         self.serialEmitter.unregister(self, self.receiveOnlineWalletList)
  129. +
  130. +         dlg = DlgImportSerialWallet(self, self, response)
  131. +         if dlg.exec_():
  132. +            id = str(dlg.combo.itemData(dlg.combo.currentIndex()).toString())
  133. +
  134. +            if self.walletMap.has_key(id):
  135. +               QMessageBox.warning(self, 'Duplicate Wallet!',\
  136. +                  'You selected a wallet that has the same ID as one already '
  137. +                  'in your wallet (%s)!  If you would like to import it anyway, '
  138. +                  'please delete the duplicate wallet in Armory, first.'% id,\
  139. +                  QMessageBox.Ok)
  140. +               return
  141. +
  142. +            self.serialEmitter.register(self, self.receiveOnlineWalletDetail)
  143. +            request = pb.OnlineWalletRequest()
  144. +            request.uniqueIDB58 = id
  145. +            request.metadataOnly = False
  146. +            self.serialConnection.write(request)
  147. +
  148. +            # rough estimate of the wallet size, divided by the byte rate of the
  149. +            # serial port multiplied by a fudge factor of 3
  150. +            sleepTime = 65536 / (self.getSettingOrSetDefault("Serial_Rate", 115200) / 8) * 3
  151. +
  152. +            DlgExecLongProcess(lambda: time.sleep(sleepTime),
  153. +               'Retrieving watching-only copy of '
  154. +               '%s from offline device...' % id, self, self).exec_()
  155. +
  156. +   def receiveOnlineWalletDetail(self, response):
  157. +      if isinstance(response, pb.OnlineWalletResponse):
  158. +         self.serialEmitter.unregister(self, self.receiveOnlineWalletDetail)
  159. +
  160. +         id = (wlt.uniqueIDB58 for wlt in response.wallets).next()
  161. +         wltData = (w for w in response.wallets if w.uniqueIDB58 == id).next()
  162. +         unpacker = BinaryUnpacker(zlib.decompress(wltData.packedBytes))
  163. +         newWlt = PyBtcWallet().readWalletData(unpacker, doScanNow=True, fileUpdate=False)
  164. +         newWlt.fillAddressPool()
  165. +         newWlt.walletPath = os.path.join(ARMORY_HOME_DIR, 'armory_%s_.watchonly.wallet' % newWlt.uniqueIDB58)
  166. +         newWlt.writeFreshWalletFile(newWlt.getWalletPath())
  167. +         newWlt.writeFreshWalletFile(newWlt.getWalletPath('backup'))
  168. +
  169. +         if TheBDM.getBDMState() in ('Uninitialized', 'Offline'):
  170. +            self.addWalletToApplication(newWlt, walletIsNew=False)
  171. +            return
  172. +
  173. +         if TheBDM.getBDMState()=='BlockchainReady':
  174. +            doRescanNow = QMessageBox.question(self, 'Rescan Needed',\
  175. +               'The wallet was imported successfully, but cannot be displayed '
  176. +               'until the global transaction history is '
  177. +               'searched for previous transactions.  This scan will potentially '
  178. +               'take much longer than a regular rescan, and the wallet cannot '
  179. +               'be shown on the main display until this rescan is complete.'
  180. +               '<br><br>'
  181. +               '<b>Would you like to go into offline mode to start this scan now?'
  182. +               '</b>  If you click "No" the scan will be aborted, and the wallet '
  183. +               'will not be added to Armory.',\
  184. +               QMessageBox.Yes | QMessageBox.No)
  185. +         else:
  186. +            doRescanNow = QMessageBox.question(self, 'Rescan Needed',\
  187. +               'The wallet was imported successfully, but its balance cannot '
  188. +               'be determined until Armory performs a "recovery scan" for the '
  189. +               'wallet.  This scan potentially takes much longer than a regular '
  190. +               'scan, and must be completed for all imported wallets. '
  191. +               '<br><br>'
  192. +               'Armory is already in the middle of a scan and cannot be interrupted. '
  193. +               'Would you like to start the recovery scan when it is done?'
  194. +               '<br><br>'
  195. +               '</b>  If you click "No," the wallet import will be aborted '
  196. +               'and you must re-import the wallet when you '
  197. +               'are able to wait for the recovery scan.',\
  198. +               QMessageBox.Yes | QMessageBox.No)
  199. +
  200. +         if doRescanNow == QMessageBox.Yes:
  201. +            LOGINFO('User requested rescan after wallet import')
  202. +            TheBDM.startWalletRecoveryScan(newWlt)
  203. +            self.setDashboardDetails()
  204. +         else:
  205. +            LOGINFO('User aborted the wallet-import scan')
  206. +            QMessageBox.warning(self, 'Import Failed',\
  207. +               'The wallet was not imported.', QMessageBox.Ok)
  208. +
  209. +            # The wallet cannot exist without also being on disk.
  210. +            # If the user aborted, we should remove the disk data.
  211. +            thepath       = newWlt.getWalletPath()
  212. +            thepathBackup = newWlt.getWalletPath('backup')
  213. +            os.remove(thepath)
  214. +            os.remove(thepathBackup)
  215. +            return
  216. +
  217. +         #self.addWalletToApplication(newWlt, walletIsNew=False)
  218. +         self.newWalletList.append([newWlt, False])
  219. +         LOGINFO('Import Complete!')
  220. +
  221.     #############################################################################
  222.     def execMigrateSatoshi(self):
  223.        reply = MsgBoxCustom(MSGBOX.Question, 'Wallet Version Warning', \
  224. @@ -3340,7 +3477,7 @@
  225.              
  226.                 if self.netMode==NETWORKMODE.Full:
  227.                    self.lblArmoryStatus.setText(\
  228. -                     '<font color=%s>Connected (%s blocks)</font> ' % \
  229. +                     '<font color=%s>Bitcoin Connected (%s blocks)</font> ' % \
  230.                       (htmlColor('TextGreen'), self.currBlockNum))
  231.        
  232.                 # Update the wallet view to immediately reflect new balances
  233. @@ -3496,6 +3633,7 @@
  234.        elif doClose or moc=='Close':
  235.           self.doShutdown = True
  236.           TheBDM.execCleanShutdown(wait=False)
  237. +         self.serialConnection.close()
  238.           self.sysTray.hide()
  239.           self.closeForReal(event)
  240.        else:
  241. Index: serial.proto
  242. IDEA additional info:
  243. Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
  244. <+>UTF-8
  245. ===================================================================
  246. --- serial.proto    (revision )
  247. +++ serial.proto    (revision )
  248. @@ -0,0 +1,72 @@
  249. +message Heartbeat {
  250. +  required string guid = 1;
  251. +}
  252. +
  253. +message SignatureRequest {
  254. +  enum SignatureType {
  255. +    PARTIAL = 1;
  256. +    FULL = 2;
  257. +  }
  258. +
  259. +  required WalletIdentifer wallet = 1;
  260. +  required SignatureType type = 2;
  261. +  required string txDP = 3;
  262. +  optional bool keepWalletUnlocked = 4;
  263. +}
  264. +
  265. +message SignatureResponse {
  266. +  required string txDP = 1;
  267. +}
  268. +
  269. +message OnlineWalletRequest {
  270. +  optional string uniqueIDB58 = 1;
  271. +  optional bool metadataOnly = 2 [default = true];
  272. +}
  273. +
  274. +message OnlineWalletResponse {
  275. +  repeated OnlineWallet wallets = 1;
  276. +}
  277. +
  278. +message InputRequest {
  279. +  required string prompt = 1;
  280. +  optional string validResponseCharacters = 2;
  281. +}
  282. +
  283. +message InputResponse {
  284. +  required string input = 1;
  285. +}
  286. +
  287. +message Notification {
  288. +  enum NotificationType {
  289. +    INFORMATION = 1;
  290. +    ERROR = 2;
  291. +    CRITICAL_ERROR = 3;
  292. +  }
  293. +
  294. +  required NotificationType type = 1;
  295. +  required string message = 2;
  296. +}
  297. +
  298. +message CreateWallet {
  299. +  required string name = 1;
  300. +  optional string description = 2;
  301. +  required bool useEncryption = 3;
  302. +  optional int32 targetComputeTime = 4;
  303. +  optional int32 maxMemoryUsage = 5;
  304. +}
  305. +
  306. +message Reset {
  307. +  required bool shutdown = 1;
  308. +}
  309. +
  310. +message WalletIdentifer {
  311. +  required string uniqueIDB58 = 1;
  312. +  required int32 lastComputedChainIndex = 2;
  313. +}
  314. +
  315. +message OnlineWallet {
  316. +  required string uniqueIDB58 = 1;
  317. +  required string labelName = 2;
  318. +  required string labelDescr = 3;
  319. +  optional bytes packedBytes = 4;
  320. +}
  321. \ No newline at end of file
  322. Index: serial_pb2.py
  323. IDEA additional info:
  324. Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
  325. <+>UTF-8
  326. ===================================================================
  327. --- serial_pb2.py   (revision )
  328. +++ serial_pb2.py   (revision )
  329. @@ -0,0 +1,591 @@
  330. +# Generated by the protocol buffer compiler.  DO NOT EDIT!
  331. +
  332. +from google.protobuf import descriptor
  333. +from google.protobuf import message
  334. +from google.protobuf import reflection
  335. +from google.protobuf import descriptor_pb2
  336. +# @@protoc_insertion_point(imports)
  337. +
  338. +
  339. +
  340. +DESCRIPTOR = descriptor.FileDescriptor(
  341. +  name='serial.proto',
  342. +  package='',
  343. +  serialized_pb='\n\x0cserial.proto\"\x19\n\tHeartbeat\x12\x0c\n\x04guid\x18\x01 \x02(\t\"\xb5\x01\n\x10SignatureRequest\x12 \n\x06wallet\x18\x01 \x02(\x0b\x32\x10.WalletIdentifer\x12-\n\x04type\x18\x02 \x02(\x0e\x32\x1f.SignatureRequest.SignatureType\x12\x0c\n\x04txDP\x18\x03 \x02(\t\x12\x1a\n\x12keepWalletUnlocked\x18\x04 \x01(\x08\"&\n\rSignatureType\x12\x0b\n\x07PARTIAL\x10\x01\x12\x08\n\x04\x46ULL\x10\x02\"!\n\x11SignatureResponse\x12\x0c\n\x04txDP\x18\x01 \x02(\t\"F\n\x13OnlineWalletRequest\x12\x13\n\x0buniqueIDB58\x18\x01 \x01(\t\x12\x1a\n\x0cmetadataOnly\x18\x02 \x01(\x08:\x04true\"6\n\x14OnlineWalletResponse\x12\x1e\n\x07wallets\x18\x01 \x03(\x0b\x32\r.OnlineWallet\"?\n\x0cInputRequest\x12\x0e\n\x06prompt\x18\x01 \x02(\t\x12\x1f\n\x17validResponseCharacters\x18\x02 \x01(\t\"\x1e\n\rInputResponse\x12\r\n\x05input\x18\x01 \x02(\t\"\x91\x01\n\x0cNotification\x12,\n\x04type\x18\x01 \x02(\x0e\x32\x1e.Notification.NotificationType\x12\x0f\n\x07message\x18\x02 \x02(\t\"B\n\x10NotificationType\x12\x0f\n\x0bINFORMATION\x10\x01\x12\t\n\x05\x45RROR\x10\x02\x12\x12\n\x0e\x43RITICAL_ERROR\x10\x03\"{\n\x0c\x43reateWallet\x12\x0c\n\x04name\x18\x01 \x02(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x15\n\ruseEncryption\x18\x03 \x02(\x08\x12\x19\n\x11targetComputeTime\x18\x04 \x01(\x05\x12\x16\n\x0emaxMemoryUsage\x18\x05 \x01(\x05\"\x19\n\x05Reset\x12\x10\n\x08shutdown\x18\x01 \x02(\x08\"F\n\x0fWalletIdentifer\x12\x13\n\x0buniqueIDB58\x18\x01 \x02(\t\x12\x1e\n\x16lastComputedChainIndex\x18\x02 \x02(\x05\"_\n\x0cOnlineWallet\x12\x13\n\x0buniqueIDB58\x18\x01 \x02(\t\x12\x11\n\tlabelName\x18\x02 \x02(\t\x12\x12\n\nlabelDescr\x18\x03 \x02(\t\x12\x13\n\x0bpackedBytes\x18\x04 \x01(\x0c')
  344. +
  345. +
  346. +
  347. +_SIGNATUREREQUEST_SIGNATURETYPE = descriptor.EnumDescriptor(
  348. +  name='SignatureType',
  349. +  full_name='SignatureRequest.SignatureType',
  350. +  filename=None,
  351. +  file=DESCRIPTOR,
  352. +  values=[
  353. +    descriptor.EnumValueDescriptor(
  354. +      name='PARTIAL', index=0, number=1,
  355. +      options=None,
  356. +      type=None),
  357. +    descriptor.EnumValueDescriptor(
  358. +      name='FULL', index=1, number=2,
  359. +      options=None,
  360. +      type=None),
  361. +  ],
  362. +  containing_type=None,
  363. +  options=None,
  364. +  serialized_start=187,
  365. +  serialized_end=225,
  366. +)
  367. +
  368. +_NOTIFICATION_NOTIFICATIONTYPE = descriptor.EnumDescriptor(
  369. +  name='NotificationType',
  370. +  full_name='Notification.NotificationType',
  371. +  filename=None,
  372. +  file=DESCRIPTOR,
  373. +  values=[
  374. +    descriptor.EnumValueDescriptor(
  375. +      name='INFORMATION', index=0, number=1,
  376. +      options=None,
  377. +      type=None),
  378. +    descriptor.EnumValueDescriptor(
  379. +      name='ERROR', index=1, number=2,
  380. +      options=None,
  381. +      type=None),
  382. +    descriptor.EnumValueDescriptor(
  383. +      name='CRITICAL_ERROR', index=2, number=3,
  384. +      options=None,
  385. +      type=None),
  386. +  ],
  387. +  containing_type=None,
  388. +  options=None,
  389. +  serialized_start=567,
  390. +  serialized_end=633,
  391. +)
  392. +
  393. +
  394. +_HEARTBEAT = descriptor.Descriptor(
  395. +  name='Heartbeat',
  396. +  full_name='Heartbeat',
  397. +  filename=None,
  398. +  file=DESCRIPTOR,
  399. +  containing_type=None,
  400. +  fields=[
  401. +    descriptor.FieldDescriptor(
  402. +      name='guid', full_name='Heartbeat.guid', index=0,
  403. +      number=1, type=9, cpp_type=9, label=2,
  404. +      has_default_value=False, default_value=unicode("", "utf-8"),
  405. +      message_type=None, enum_type=None, containing_type=None,
  406. +      is_extension=False, extension_scope=None,
  407. +      options=None),
  408. +  ],
  409. +  extensions=[
  410. +  ],
  411. +  nested_types=[],
  412. +  enum_types=[
  413. +  ],
  414. +  options=None,
  415. +  is_extendable=False,
  416. +  extension_ranges=[],
  417. +  serialized_start=16,
  418. +  serialized_end=41,
  419. +)
  420. +
  421. +
  422. +_SIGNATUREREQUEST = descriptor.Descriptor(
  423. +  name='SignatureRequest',
  424. +  full_name='SignatureRequest',
  425. +  filename=None,
  426. +  file=DESCRIPTOR,
  427. +  containing_type=None,
  428. +  fields=[
  429. +    descriptor.FieldDescriptor(
  430. +      name='wallet', full_name='SignatureRequest.wallet', index=0,
  431. +      number=1, type=11, cpp_type=10, label=2,
  432. +      has_default_value=False, default_value=None,
  433. +      message_type=None, enum_type=None, containing_type=None,
  434. +      is_extension=False, extension_scope=None,
  435. +      options=None),
  436. +    descriptor.FieldDescriptor(
  437. +      name='type', full_name='SignatureRequest.type', index=1,
  438. +      number=2, type=14, cpp_type=8, label=2,
  439. +      has_default_value=False, default_value=1,
  440. +      message_type=None, enum_type=None, containing_type=None,
  441. +      is_extension=False, extension_scope=None,
  442. +      options=None),
  443. +    descriptor.FieldDescriptor(
  444. +      name='txDP', full_name='SignatureRequest.txDP', index=2,
  445. +      number=3, type=9, cpp_type=9, label=2,
  446. +      has_default_value=False, default_value=unicode("", "utf-8"),
  447. +      message_type=None, enum_type=None, containing_type=None,
  448. +      is_extension=False, extension_scope=None,
  449. +      options=None),
  450. +    descriptor.FieldDescriptor(
  451. +      name='keepWalletUnlocked', full_name='SignatureRequest.keepWalletUnlocked', index=3,
  452. +      number=4, type=8, cpp_type=7, label=1,
  453. +      has_default_value=False, default_value=False,
  454. +      message_type=None, enum_type=None, containing_type=None,
  455. +      is_extension=False, extension_scope=None,
  456. +      options=None),
  457. +  ],
  458. +  extensions=[
  459. +  ],
  460. +  nested_types=[],
  461. +  enum_types=[
  462. +    _SIGNATUREREQUEST_SIGNATURETYPE,
  463. +  ],
  464. +  options=None,
  465. +  is_extendable=False,
  466. +  extension_ranges=[],
  467. +  serialized_start=44,
  468. +  serialized_end=225,
  469. +)
  470. +
  471. +
  472. +_SIGNATURERESPONSE = descriptor.Descriptor(
  473. +  name='SignatureResponse',
  474. +  full_name='SignatureResponse',
  475. +  filename=None,
  476. +  file=DESCRIPTOR,
  477. +  containing_type=None,
  478. +  fields=[
  479. +    descriptor.FieldDescriptor(
  480. +      name='txDP', full_name='SignatureResponse.txDP', index=0,
  481. +      number=1, type=9, cpp_type=9, label=2,
  482. +      has_default_value=False, default_value=unicode("", "utf-8"),
  483. +      message_type=None, enum_type=None, containing_type=None,
  484. +      is_extension=False, extension_scope=None,
  485. +      options=None),
  486. +  ],
  487. +  extensions=[
  488. +  ],
  489. +  nested_types=[],
  490. +  enum_types=[
  491. +  ],
  492. +  options=None,
  493. +  is_extendable=False,
  494. +  extension_ranges=[],
  495. +  serialized_start=227,
  496. +  serialized_end=260,
  497. +)
  498. +
  499. +
  500. +_ONLINEWALLETREQUEST = descriptor.Descriptor(
  501. +  name='OnlineWalletRequest',
  502. +  full_name='OnlineWalletRequest',
  503. +  filename=None,
  504. +  file=DESCRIPTOR,
  505. +  containing_type=None,
  506. +  fields=[
  507. +    descriptor.FieldDescriptor(
  508. +      name='uniqueIDB58', full_name='OnlineWalletRequest.uniqueIDB58', index=0,
  509. +      number=1, type=9, cpp_type=9, label=1,
  510. +      has_default_value=False, default_value=unicode("", "utf-8"),
  511. +      message_type=None, enum_type=None, containing_type=None,
  512. +      is_extension=False, extension_scope=None,
  513. +      options=None),
  514. +    descriptor.FieldDescriptor(
  515. +      name='metadataOnly', full_name='OnlineWalletRequest.metadataOnly', index=1,
  516. +      number=2, type=8, cpp_type=7, label=1,
  517. +      has_default_value=True, default_value=True,
  518. +      message_type=None, enum_type=None, containing_type=None,
  519. +      is_extension=False, extension_scope=None,
  520. +      options=None),
  521. +  ],
  522. +  extensions=[
  523. +  ],
  524. +  nested_types=[],
  525. +  enum_types=[
  526. +  ],
  527. +  options=None,
  528. +  is_extendable=False,
  529. +  extension_ranges=[],
  530. +  serialized_start=262,
  531. +  serialized_end=332,
  532. +)
  533. +
  534. +
  535. +_ONLINEWALLETRESPONSE = descriptor.Descriptor(
  536. +  name='OnlineWalletResponse',
  537. +  full_name='OnlineWalletResponse',
  538. +  filename=None,
  539. +  file=DESCRIPTOR,
  540. +  containing_type=None,
  541. +  fields=[
  542. +    descriptor.FieldDescriptor(
  543. +      name='wallets', full_name='OnlineWalletResponse.wallets', index=0,
  544. +      number=1, type=11, cpp_type=10, label=3,
  545. +      has_default_value=False, default_value=[],
  546. +      message_type=None, enum_type=None, containing_type=None,
  547. +      is_extension=False, extension_scope=None,
  548. +      options=None),
  549. +  ],
  550. +  extensions=[
  551. +  ],
  552. +  nested_types=[],
  553. +  enum_types=[
  554. +  ],
  555. +  options=None,
  556. +  is_extendable=False,
  557. +  extension_ranges=[],
  558. +  serialized_start=334,
  559. +  serialized_end=388,
  560. +)
  561. +
  562. +
  563. +_INPUTREQUEST = descriptor.Descriptor(
  564. +  name='InputRequest',
  565. +  full_name='InputRequest',
  566. +  filename=None,
  567. +  file=DESCRIPTOR,
  568. +  containing_type=None,
  569. +  fields=[
  570. +    descriptor.FieldDescriptor(
  571. +      name='prompt', full_name='InputRequest.prompt', index=0,
  572. +      number=1, type=9, cpp_type=9, label=2,
  573. +      has_default_value=False, default_value=unicode("", "utf-8"),
  574. +      message_type=None, enum_type=None, containing_type=None,
  575. +      is_extension=False, extension_scope=None,
  576. +      options=None),
  577. +    descriptor.FieldDescriptor(
  578. +      name='validResponseCharacters', full_name='InputRequest.validResponseCharacters', index=1,
  579. +      number=2, type=9, cpp_type=9, label=1,
  580. +      has_default_value=False, default_value=unicode("", "utf-8"),
  581. +      message_type=None, enum_type=None, containing_type=None,
  582. +      is_extension=False, extension_scope=None,
  583. +      options=None),
  584. +  ],
  585. +  extensions=[
  586. +  ],
  587. +  nested_types=[],
  588. +  enum_types=[
  589. +  ],
  590. +  options=None,
  591. +  is_extendable=False,
  592. +  extension_ranges=[],
  593. +  serialized_start=390,
  594. +  serialized_end=453,
  595. +)
  596. +
  597. +
  598. +_INPUTRESPONSE = descriptor.Descriptor(
  599. +  name='InputResponse',
  600. +  full_name='InputResponse',
  601. +  filename=None,
  602. +  file=DESCRIPTOR,
  603. +  containing_type=None,
  604. +  fields=[
  605. +    descriptor.FieldDescriptor(
  606. +      name='input', full_name='InputResponse.input', index=0,
  607. +      number=1, type=9, cpp_type=9, label=2,
  608. +      has_default_value=False, default_value=unicode("", "utf-8"),
  609. +      message_type=None, enum_type=None, containing_type=None,
  610. +      is_extension=False, extension_scope=None,
  611. +      options=None),
  612. +  ],
  613. +  extensions=[
  614. +  ],
  615. +  nested_types=[],
  616. +  enum_types=[
  617. +  ],
  618. +  options=None,
  619. +  is_extendable=False,
  620. +  extension_ranges=[],
  621. +  serialized_start=455,
  622. +  serialized_end=485,
  623. +)
  624. +
  625. +
  626. +_NOTIFICATION = descriptor.Descriptor(
  627. +  name='Notification',
  628. +  full_name='Notification',
  629. +  filename=None,
  630. +  file=DESCRIPTOR,
  631. +  containing_type=None,
  632. +  fields=[
  633. +    descriptor.FieldDescriptor(
  634. +      name='type', full_name='Notification.type', index=0,
  635. +      number=1, type=14, cpp_type=8, label=2,
  636. +      has_default_value=False, default_value=1,
  637. +      message_type=None, enum_type=None, containing_type=None,
  638. +      is_extension=False, extension_scope=None,
  639. +      options=None),
  640. +    descriptor.FieldDescriptor(
  641. +      name='message', full_name='Notification.message', index=1,
  642. +      number=2, type=9, cpp_type=9, label=2,
  643. +      has_default_value=False, default_value=unicode("", "utf-8"),
  644. +      message_type=None, enum_type=None, containing_type=None,
  645. +      is_extension=False, extension_scope=None,
  646. +      options=None),
  647. +  ],
  648. +  extensions=[
  649. +  ],
  650. +  nested_types=[],
  651. +  enum_types=[
  652. +    _NOTIFICATION_NOTIFICATIONTYPE,
  653. +  ],
  654. +  options=None,
  655. +  is_extendable=False,
  656. +  extension_ranges=[],
  657. +  serialized_start=488,
  658. +  serialized_end=633,
  659. +)
  660. +
  661. +
  662. +_CREATEWALLET = descriptor.Descriptor(
  663. +  name='CreateWallet',
  664. +  full_name='CreateWallet',
  665. +  filename=None,
  666. +  file=DESCRIPTOR,
  667. +  containing_type=None,
  668. +  fields=[
  669. +    descriptor.FieldDescriptor(
  670. +      name='name', full_name='CreateWallet.name', index=0,
  671. +      number=1, type=9, cpp_type=9, label=2,
  672. +      has_default_value=False, default_value=unicode("", "utf-8"),
  673. +      message_type=None, enum_type=None, containing_type=None,
  674. +      is_extension=False, extension_scope=None,
  675. +      options=None),
  676. +    descriptor.FieldDescriptor(
  677. +      name='description', full_name='CreateWallet.description', index=1,
  678. +      number=2, type=9, cpp_type=9, label=1,
  679. +      has_default_value=False, default_value=unicode("", "utf-8"),
  680. +      message_type=None, enum_type=None, containing_type=None,
  681. +      is_extension=False, extension_scope=None,
  682. +      options=None),
  683. +    descriptor.FieldDescriptor(
  684. +      name='useEncryption', full_name='CreateWallet.useEncryption', index=2,
  685. +      number=3, type=8, cpp_type=7, label=2,
  686. +      has_default_value=False, default_value=False,
  687. +      message_type=None, enum_type=None, containing_type=None,
  688. +      is_extension=False, extension_scope=None,
  689. +      options=None),
  690. +    descriptor.FieldDescriptor(
  691. +      name='targetComputeTime', full_name='CreateWallet.targetComputeTime', index=3,
  692. +      number=4, type=5, cpp_type=1, label=1,
  693. +      has_default_value=False, default_value=0,
  694. +      message_type=None, enum_type=None, containing_type=None,
  695. +      is_extension=False, extension_scope=None,
  696. +      options=None),
  697. +    descriptor.FieldDescriptor(
  698. +      name='maxMemoryUsage', full_name='CreateWallet.maxMemoryUsage', index=4,
  699. +      number=5, type=5, cpp_type=1, label=1,
  700. +      has_default_value=False, default_value=0,
  701. +      message_type=None, enum_type=None, containing_type=None,
  702. +      is_extension=False, extension_scope=None,
  703. +      options=None),
  704. +  ],
  705. +  extensions=[
  706. +  ],
  707. +  nested_types=[],
  708. +  enum_types=[
  709. +  ],
  710. +  options=None,
  711. +  is_extendable=False,
  712. +  extension_ranges=[],
  713. +  serialized_start=635,
  714. +  serialized_end=758,
  715. +)
  716. +
  717. +
  718. +_RESET = descriptor.Descriptor(
  719. +  name='Reset',
  720. +  full_name='Reset',
  721. +  filename=None,
  722. +  file=DESCRIPTOR,
  723. +  containing_type=None,
  724. +  fields=[
  725. +    descriptor.FieldDescriptor(
  726. +      name='shutdown', full_name='Reset.shutdown', index=0,
  727. +      number=1, type=8, cpp_type=7, label=2,
  728. +      has_default_value=False, default_value=False,
  729. +      message_type=None, enum_type=None, containing_type=None,
  730. +      is_extension=False, extension_scope=None,
  731. +      options=None),
  732. +  ],
  733. +  extensions=[
  734. +  ],
  735. +  nested_types=[],
  736. +  enum_types=[
  737. +  ],
  738. +  options=None,
  739. +  is_extendable=False,
  740. +  extension_ranges=[],
  741. +  serialized_start=760,
  742. +  serialized_end=785,
  743. +)
  744. +
  745. +
  746. +_WALLETIDENTIFER = descriptor.Descriptor(
  747. +  name='WalletIdentifer',
  748. +  full_name='WalletIdentifer',
  749. +  filename=None,
  750. +  file=DESCRIPTOR,
  751. +  containing_type=None,
  752. +  fields=[
  753. +    descriptor.FieldDescriptor(
  754. +      name='uniqueIDB58', full_name='WalletIdentifer.uniqueIDB58', index=0,
  755. +      number=1, type=9, cpp_type=9, label=2,
  756. +      has_default_value=False, default_value=unicode("", "utf-8"),
  757. +      message_type=None, enum_type=None, containing_type=None,
  758. +      is_extension=False, extension_scope=None,
  759. +      options=None),
  760. +    descriptor.FieldDescriptor(
  761. +      name='lastComputedChainIndex', full_name='WalletIdentifer.lastComputedChainIndex', index=1,
  762. +      number=2, type=5, cpp_type=1, label=2,
  763. +      has_default_value=False, default_value=0,
  764. +      message_type=None, enum_type=None, containing_type=None,
  765. +      is_extension=False, extension_scope=None,
  766. +      options=None),
  767. +  ],
  768. +  extensions=[
  769. +  ],
  770. +  nested_types=[],
  771. +  enum_types=[
  772. +  ],
  773. +  options=None,
  774. +  is_extendable=False,
  775. +  extension_ranges=[],
  776. +  serialized_start=787,
  777. +  serialized_end=857,
  778. +)
  779. +
  780. +
  781. +_ONLINEWALLET = descriptor.Descriptor(
  782. +  name='OnlineWallet',
  783. +  full_name='OnlineWallet',
  784. +  filename=None,
  785. +  file=DESCRIPTOR,
  786. +  containing_type=None,
  787. +  fields=[
  788. +    descriptor.FieldDescriptor(
  789. +      name='uniqueIDB58', full_name='OnlineWallet.uniqueIDB58', index=0,
  790. +      number=1, type=9, cpp_type=9, label=2,
  791. +      has_default_value=False, default_value=unicode("", "utf-8"),
  792. +      message_type=None, enum_type=None, containing_type=None,
  793. +      is_extension=False, extension_scope=None,
  794. +      options=None),
  795. +    descriptor.FieldDescriptor(
  796. +      name='labelName', full_name='OnlineWallet.labelName', index=1,
  797. +      number=2, type=9, cpp_type=9, label=2,
  798. +      has_default_value=False, default_value=unicode("", "utf-8"),
  799. +      message_type=None, enum_type=None, containing_type=None,
  800. +      is_extension=False, extension_scope=None,
  801. +      options=None),
  802. +    descriptor.FieldDescriptor(
  803. +      name='labelDescr', full_name='OnlineWallet.labelDescr', index=2,
  804. +      number=3, type=9, cpp_type=9, label=2,
  805. +      has_default_value=False, default_value=unicode("", "utf-8"),
  806. +      message_type=None, enum_type=None, containing_type=None,
  807. +      is_extension=False, extension_scope=None,
  808. +      options=None),
  809. +    descriptor.FieldDescriptor(
  810. +      name='packedBytes', full_name='OnlineWallet.packedBytes', index=3,
  811. +      number=4, type=12, cpp_type=9, label=1,
  812. +      has_default_value=False, default_value="",
  813. +      message_type=None, enum_type=None, containing_type=None,
  814. +      is_extension=False, extension_scope=None,
  815. +      options=None),
  816. +  ],
  817. +  extensions=[
  818. +  ],
  819. +  nested_types=[],
  820. +  enum_types=[
  821. +  ],
  822. +  options=None,
  823. +  is_extendable=False,
  824. +  extension_ranges=[],
  825. +  serialized_start=859,
  826. +  serialized_end=954,
  827. +)
  828. +
  829. +_SIGNATUREREQUEST.fields_by_name['wallet'].message_type = _WALLETIDENTIFER
  830. +_SIGNATUREREQUEST.fields_by_name['type'].enum_type = _SIGNATUREREQUEST_SIGNATURETYPE
  831. +_SIGNATUREREQUEST_SIGNATURETYPE.containing_type = _SIGNATUREREQUEST;
  832. +_ONLINEWALLETRESPONSE.fields_by_name['wallets'].message_type = _ONLINEWALLET
  833. +_NOTIFICATION.fields_by_name['type'].enum_type = _NOTIFICATION_NOTIFICATIONTYPE
  834. +_NOTIFICATION_NOTIFICATIONTYPE.containing_type = _NOTIFICATION;
  835. +DESCRIPTOR.message_types_by_name['Heartbeat'] = _HEARTBEAT
  836. +DESCRIPTOR.message_types_by_name['SignatureRequest'] = _SIGNATUREREQUEST
  837. +DESCRIPTOR.message_types_by_name['SignatureResponse'] = _SIGNATURERESPONSE
  838. +DESCRIPTOR.message_types_by_name['OnlineWalletRequest'] = _ONLINEWALLETREQUEST
  839. +DESCRIPTOR.message_types_by_name['OnlineWalletResponse'] = _ONLINEWALLETRESPONSE
  840. +DESCRIPTOR.message_types_by_name['InputRequest'] = _INPUTREQUEST
  841. +DESCRIPTOR.message_types_by_name['InputResponse'] = _INPUTRESPONSE
  842. +DESCRIPTOR.message_types_by_name['Notification'] = _NOTIFICATION
  843. +DESCRIPTOR.message_types_by_name['CreateWallet'] = _CREATEWALLET
  844. +DESCRIPTOR.message_types_by_name['Reset'] = _RESET
  845. +DESCRIPTOR.message_types_by_name['WalletIdentifer'] = _WALLETIDENTIFER
  846. +DESCRIPTOR.message_types_by_name['OnlineWallet'] = _ONLINEWALLET
  847. +
  848. +class Heartbeat(message.Message):
  849. +  __metaclass__ = reflection.GeneratedProtocolMessageType
  850. +  DESCRIPTOR = _HEARTBEAT
  851. +  
  852. +  # @@protoc_insertion_point(class_scope:Heartbeat)
  853. +
  854. +class SignatureRequest(message.Message):
  855. +  __metaclass__ = reflection.GeneratedProtocolMessageType
  856. +  DESCRIPTOR = _SIGNATUREREQUEST
  857. +  
  858. +  # @@protoc_insertion_point(class_scope:SignatureRequest)
  859. +
  860. +class SignatureResponse(message.Message):
  861. +  __metaclass__ = reflection.GeneratedProtocolMessageType
  862. +  DESCRIPTOR = _SIGNATURERESPONSE
  863. +  
  864. +  # @@protoc_insertion_point(class_scope:SignatureResponse)
  865. +
  866. +class OnlineWalletRequest(message.Message):
  867. +  __metaclass__ = reflection.GeneratedProtocolMessageType
  868. +  DESCRIPTOR = _ONLINEWALLETREQUEST
  869. +  
  870. +  # @@protoc_insertion_point(class_scope:OnlineWalletRequest)
  871. +
  872. +class OnlineWalletResponse(message.Message):
  873. +  __metaclass__ = reflection.GeneratedProtocolMessageType
  874. +  DESCRIPTOR = _ONLINEWALLETRESPONSE
  875. +  
  876. +  # @@protoc_insertion_point(class_scope:OnlineWalletResponse)
  877. +
  878. +class InputRequest(message.Message):
  879. +  __metaclass__ = reflection.GeneratedProtocolMessageType
  880. +  DESCRIPTOR = _INPUTREQUEST
  881. +  
  882. +  # @@protoc_insertion_point(class_scope:InputRequest)
  883. +
  884. +class InputResponse(message.Message):
  885. +  __metaclass__ = reflection.GeneratedProtocolMessageType
  886. +  DESCRIPTOR = _INPUTRESPONSE
  887. +  
  888. +  # @@protoc_insertion_point(class_scope:InputResponse)
  889. +
  890. +class Notification(message.Message):
  891. +  __metaclass__ = reflection.GeneratedProtocolMessageType
  892. +  DESCRIPTOR = _NOTIFICATION
  893. +  
  894. +  # @@protoc_insertion_point(class_scope:Notification)
  895. +
  896. +class CreateWallet(message.Message):
  897. +  __metaclass__ = reflection.GeneratedProtocolMessageType
  898. +  DESCRIPTOR = _CREATEWALLET
  899. +  
  900. +  # @@protoc_insertion_point(class_scope:CreateWallet)
  901. +
  902. +class Reset(message.Message):
  903. +  __metaclass__ = reflection.GeneratedProtocolMessageType
  904. +  DESCRIPTOR = _RESET
  905. +  
  906. +  # @@protoc_insertion_point(class_scope:Reset)
  907. +
  908. +class WalletIdentifer(message.Message):
  909. +  __metaclass__ = reflection.GeneratedProtocolMessageType
  910. +  DESCRIPTOR = _WALLETIDENTIFER
  911. +  
  912. +  # @@protoc_insertion_point(class_scope:WalletIdentifer)
  913. +
  914. +class OnlineWallet(message.Message):
  915. +  __metaclass__ = reflection.GeneratedProtocolMessageType
  916. +  DESCRIPTOR = _ONLINEWALLET
  917. +  
  918. +  # @@protoc_insertion_point(class_scope:OnlineWallet)
  919. +
  920. +# @@protoc_insertion_point(module_scope)
  921. Index: qtdialogs.py
  922. IDEA additional info:
  923. Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
  924. <+>UTF-8
  925. ===================================================================
  926. --- qtdialogs.py    (revision e1b435821967518f46f5d1722bf6245e5e011e29)
  927. +++ qtdialogs.py    (revision )
  928. @@ -15,6 +15,7 @@
  929.  from armorymodels import *
  930.  from armorycolors import Colors, htmlColor
  931.  import qrc_img_resources
  932. +import serial_pb2 as pb
  933.  
  934.  MIN_PASSWD_WIDTH = lambda obj: tightSizeStr(obj, '*'*16)[0]
  935.  
  936. @@ -2478,12 +2479,15 @@
  937.        lblImportDescr = QLabel('Chose the wallet import source:')
  938.        self.btnImportFile  = QPushButton("Import Armory wallet from &file")
  939.        self.btnImportPaper = QPushButton("Restore from &paper backup")
  940. +      self.btnImportSerial = QPushButton("Import Armory wallet from &serial")
  941. +      self.btnImportSerial.setEnabled(main.serialConnection.isConnected)
  942.        self.btnMigrate     = QPushButton("Migrate wallet.dat (main Bitcoin App)")
  943.  
  944.        self.btnImportFile.setMinimumWidth(300)
  945.  
  946.        self.connect( self.btnImportFile,  SIGNAL("clicked()"), self.acceptImport)
  947.        self.connect( self.btnImportPaper, SIGNAL('clicked()'), self.acceptPaper)
  948. +      self.connect( self.btnImportSerial, SIGNAL('clicked()'), self.acceptSerial)
  949.        self.connect( self.btnMigrate,     SIGNAL('clicked()'), self.acceptMigrate)
  950.  
  951.        ttip1 = createToolTipObject('Import an existing Armory wallet, usually with a '
  952. @@ -2495,12 +2499,16 @@
  953.                                    'a wallet, you can manually enter the wallet '
  954.                                    'data into Armory to recover the wallet.')
  955.  
  956. +      ttip3 = createToolTipObject('If you have an offline Armory server connected '
  957. +                                   'through a serial port, you can import the '
  958. +                                    'offline copy of any wallets there.')
  959. +
  960.        ttip3 = createToolTipObject('Migrate all your wallet.dat addresses '
  961.                                    'from the regular Bitcoin client to an Armory '
  962.                                    'wallet.')
  963.  
  964.        w,h = relaxedSizeStr(ttip1, '(?)')
  965. -      for ttip in (ttip1, ttip2):
  966. +      for ttip in (ttip1, ttip2, ttip3):
  967.           ttip.setMaximumSize(w,h)
  968.           ttip.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
  969.  
  970. @@ -2509,7 +2517,8 @@
  971.        layout.addWidget(lblImportDescr,      0,0, 1, 2)
  972.        layout.addWidget(self.btnImportFile,  1,0, 1, 2); layout.addWidget(ttip1, 1,2,1,1)
  973.        layout.addWidget(self.btnImportPaper, 2,0, 1, 2); layout.addWidget(ttip2, 2,2,1,1)
  974. -      #layout.addWidget(self.btnMigrate,     3,0, 1, 2); layout.addWidget(ttip3, 3,2,1,1)
  975. +      layout.addWidget(self.btnImportSerial,3,0, 1, 2); layout.addWidget(ttip3, 3,2,1,1)
  976. +      #layout.addWidget(self.btnMigrate,     4,0, 1, 2); layout.addWidget(ttip4, 4,2,1,1)
  977.  
  978.        if self.main.usermode in (USERMODE.Advanced, USERMODE.Expert):
  979.           lbl = QRichLabel('You can manually add wallets to armory by copying them '
  980. @@ -2538,12 +2547,21 @@
  981.     def acceptPaper(self):
  982.        self.importType_file    = False
  983.        self.importType_paper   = True
  984. +      self.importType_serial  = False
  985.        self.importType_migrate = False
  986.        self.accept()
  987. -      
  988. +
  989. +   def acceptSerial(self):
  990. +      self.importType_file    = False
  991. +      self.importType_paper   = False
  992. +      self.importType_serial  = True
  993. +      self.importType_migrate = False
  994. +      self.accept()
  995. +
  996.     def acceptMigrate(self):
  997.        self.importType_file    = False
  998.        self.importType_paper   = False
  999. +      self.importType_serial  = False
  1000.        self.importType_migrate = True
  1001.        self.accept()
  1002.  
  1003. @@ -3783,10 +3801,34 @@
  1004.  
  1005.        # Will pop up a little "please wait..." window while filling addr pool
  1006.        DlgExecLongProcess(fillAddrPoolAndAccept, "Recovering wallet...", self, self.main).exec_()
  1007. -      
  1008. +
  1009. +class DlgImportSerialWallet(ArmoryDialog):
  1010. +   def __init__(self, parent=None, main=None, response=None):
  1011. +      super(DlgImportSerialWallet, self).__init__(parent, main)
  1012.  
  1013. +      self.setWindowTitle('Import Wallet From Serial Device')
  1014. +      self.setWindowIcon(QIcon(self.main.iconfile))
  1015.  
  1016. +      lblImportDescr = QLabel('Choose the wallet to import:')
  1017. +      self.combo = QComboBox(self)
  1018. +      for wlt in response.wallets:
  1019. +         self.combo.addItem("%s: %s (%s)" % (wlt.uniqueIDB58, wlt.labelName, wlt.labelDescr), wlt.uniqueIDB58)
  1020.  
  1021. +      h,w = relaxedSizeNChar(self, 50)
  1022. +      self.combo.setMinimumSize(h,w)
  1023. +
  1024. +      buttonbox = QDialogButtonBox(QDialogButtonBox.Ok |\
  1025. +                                   QDialogButtonBox.Cancel)
  1026. +      self.connect(buttonbox, SIGNAL('accepted()'), self.accept)
  1027. +      self.connect(buttonbox, SIGNAL('rejected()'), self.reject)
  1028. +
  1029. +      # Set up the layout
  1030. +      layout = QVBoxLayout()
  1031. +      layout.addWidget(lblImportDescr)
  1032. +      layout.addWidget(self.combo)
  1033. +      layout.addWidget(buttonbox)
  1034. +      self.setLayout(layout)
  1035. +
  1036.  ################################################################################
  1037.  class DlgSetComment(ArmoryDialog):
  1038.     """ This will be a dumb dialog for retrieving a comment from user """
  1039. @@ -5644,6 +5686,8 @@
  1040.        self.txdp   = txdp
  1041.        self.wlt    = wlt
  1042.  
  1043. +      self.main.serialEmitter.unregister(self.receiveSerialMessage)
  1044. +
  1045.        canSign = False
  1046.        lblDescr = QRichLabel('')
  1047.        if determineWalletType(wlt, self.main)[0]==WLTTYPES.Offline:
  1048. @@ -5716,6 +5760,17 @@
  1049.           'Copy the transaction data to the clipboard, so that it can be '
  1050.           'pasted into an email or a text document.')
  1051.  
  1052. +      self.btnTransmitStart = QPushButton('Transmit over serial')
  1053. +      self.btnTransmitStart.setEnabled(self.main.serialConnection.isConnected)
  1054. +      self.connect(self.btnTransmitStart, SIGNAL('clicked()'), self.doTxSerialTransmit)
  1055. +      ttipTransmit = createToolTipObject( \
  1056. +         'Transmit the transaction data over the configured serial connection '
  1057. +         'and process the received signed transaction.')
  1058. +
  1059. +      self.btnTransmitCancel = QPushButton('Cancel transmission')
  1060. +      self.btnTransmitCancel.setVisible(False)
  1061. +      self.connect(self.btnTransmitCancel, SIGNAL('clicked()'), self.cancelTxSerialTransmit, True)
  1062. +
  1063.        lblInstruct = QRichLabel('<b>Instructions for completing this transaction:</b>')
  1064.        lblUTX = QRichLabel('<b>Transaction Data</b> \t (Unsigned ID: %s)' % txdp.uniqueB58)
  1065.        w,h = tightSizeStr(GETFONT('Fixed',8),'0'*85)[0], int(12*8.2)
  1066. @@ -5759,14 +5814,17 @@
  1067.        frmLowerLayout = QGridLayout()
  1068.        
  1069.        frmLowerLayout.addWidget(frmUTX,        0,0,  1,3)
  1070. -      frmLowerLayout.addWidget(self.txtTxDP,  1,0,  3,1)
  1071. +      frmLowerLayout.addWidget(self.txtTxDP,  1,0,  4,1)
  1072.        frmLowerLayout.addWidget(btnSave,       1,1,  1,1)
  1073.        frmLowerLayout.addWidget(ttipSave,      1,2,  1,1)
  1074.        frmLowerLayout.addWidget(btnCopy,       2,1,  1,1)
  1075.        frmLowerLayout.addWidget(ttipCopy,      2,2,  1,1)
  1076. -      frmLowerLayout.addWidget(self.lblCopied,3,1,  1,2)
  1077. +      frmLowerLayout.addWidget(self.btnTransmitStart,   3,1,  1,1)
  1078. +      frmLowerLayout.addWidget(self.btnTransmitCancel,   3,2,  1,1)
  1079. +      frmLowerLayout.addWidget(ttipTransmit,  3,3,  1,1)
  1080. +      frmLowerLayout.addWidget(self.lblCopied,4,1,  1,2)
  1081.  
  1082. -      frmLowerLayout.addWidget(nextStepStrip, 4,0,  1,3)
  1083. +      frmLowerLayout.addWidget(nextStepStrip, 5,0,  1,3)
  1084.        frmLower.setLayout(frmLowerLayout)
  1085.  
  1086.  
  1087. @@ -5796,6 +5854,49 @@
  1088.        clipb.setText(self.txtSigned.toPlainText())
  1089.        self.lblCopiedS.setText('<i>Copied!</i>')
  1090.  
  1091. +   def receiveSerialMessage(self, msg):
  1092. +      if isinstance(msg, bool):
  1093. +         self.btnTransmitStart.setEnabled(msg)
  1094. +      elif isinstance(msg, pb.Notification):
  1095. +         self.lblCopied.setText('<i>%s</i>' % msg.message)
  1096. +      elif isinstance(msg, pb.Error):
  1097. +         self.lblCopied.setText('<font color="%s">%s</font>' % (msg.message, htmlColor("TextWarn")))
  1098. +         if msg.critical:
  1099. +            self.cancelTxSerialTransmit(False)
  1100. +      elif isinstance(msg, pb.SignatureResponse):
  1101. +         self.cancelTxSerialTransmit(False)
  1102. +
  1103. +         if msg.signatureType == msg.FULL:
  1104. +            dlgRvw = DlgReviewOfflineTx(self.parent, self.main)
  1105. +            dlgRvw.txtTxDP.setText(msg.txDP)
  1106. +            dlgRvw.exec_()
  1107. +            self.accept()
  1108. +         #else:
  1109. +            # transaction signed by some, but not all of the necessary private keys
  1110. +
  1111. +   def cancelTxSerialTransmit(self, reset):
  1112. +      if reset:
  1113. +         msg = pb.Reset()
  1114. +         msg.shutdown = False
  1115. +         self.main.serialConnection.write(msg)
  1116. +
  1117. +      self.btnTransmitStart.setVisible(False)
  1118. +      self.btnTransmitCancel.setVisible(True)
  1119. +      self.lblCopied.clear()
  1120. +
  1121. +   def doTxSerialTransmit(self):
  1122. +      """ Send the Unsigned-Tx block of data to TODO: """
  1123. +      self.btnTransmitStart.setVisible(False)
  1124. +      self.btnTransmitCancel.setVisible(True)
  1125. +
  1126. +      msg = pb.SignatureRequest()
  1127. +      msg.wallet.uniqueIDB58 = self.wlt.uniqueIDB58
  1128. +      msg.wallet.lastComputedChainIndex = self.wlt.lastComputedChainIndex
  1129. +      msg.txDP = str(self.txtTxDP.toPlainText())
  1130. +
  1131. +      self.main.serialConnection.write(msg)
  1132. +      self.lblCopied.setText('Sent unsigned tx')
  1133. +
  1134.     def doSaveFile(self):
  1135.        """ Save the Unsigned-Tx block of data """
  1136.        dpid = self.txdp.uniqueB58
  1137. @@ -5925,7 +6026,13 @@
  1138.        DlgReviewOfflineTx(self, self.main).exec_()
  1139.        self.accept()
  1140.              
  1141. +   def accept(self, *args):
  1142. +      self.main.serialEmitter.unregister(self.receiveSerialMessage)
  1143. +      super(DlgOfflineTxCreated, self).accept(*args)
  1144.  
  1145. +   def reject(self, *args):
  1146. +      self.main.serialEmitter.unregister(self.receiveSerialMessage)
  1147. +      super(DlgOfflineTxCreated, self).reject(*args)
  1148.  
  1149.  ################################################################################
  1150.  class DlgOfflineSelect(ArmoryDialog):
  1151. Index: offline_serial_server.py
  1152. IDEA additional info:
  1153. Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
  1154. <+>UTF-8
  1155. ===================================================================
  1156. --- offline_serial_server.py    (revision )
  1157. +++ offline_serial_server.py    (revision )
  1158. @@ -0,0 +1,180 @@
  1159. +import sys
  1160. +from twisted.python.dist import build_scripts_twisted
  1161. +
  1162. +sys.path.append('..')
  1163. +
  1164. +from armoryengine import *
  1165. +import os
  1166. +import time
  1167. +import getpass
  1168. +import zlib
  1169. +from serial_pb2 import *
  1170. +
  1171. +class OfflineSerialServer:
  1172. +   def __init__(self):
  1173. +      if len(CLI_ARGS) < 2:
  1174. +         print 'USAGE: %s <serial device> <serial rate>' % argv[0]
  1175. +         exit()
  1176. +
  1177. +      self.wallets = dict([self.loadWallet(wlt) for wlt in os.listdir(ARMORY_HOME_DIR) if
  1178. +                           wlt.endswith('.wallet') and not wlt.endswith('_backup.wallet')])
  1179. +
  1180. +      self.device = CLI_ARGS[0]
  1181. +      self.rate = int(CLI_ARGS[1])
  1182. +
  1183. +      self.conn = PySerialConnection(self.device, self.rate, self.receiveMessage)
  1184. +
  1185. +   def __enter__(self):
  1186. +      return self
  1187. +
  1188. +   def __exit__(self, exc_type, exc_val, exc_tb):
  1189. +      self.conn.close()
  1190. +      if self.conn.port.isOpen():
  1191. +         self.conn.port.close()
  1192. +      print 'Serial port closed'
  1193. +
  1194. +   def writeInfo(self, info):
  1195. +      msg = Notification()
  1196. +      msg.type = msg.INFORMATION
  1197. +      msg.message = info
  1198. +      self.conn.write(msg)
  1199. +
  1200. +   def writeError(self, error, critical=False):
  1201. +      msg = Notification()
  1202. +      if critical:
  1203. +         msg.type = msg.CRITICAL_ERROR
  1204. +      else:
  1205. +         msg.type = msg.ERROR
  1206. +      msg.message = error
  1207. +      self.conn.write(msg)
  1208. +
  1209. +   def loadWallet(self, file):
  1210. +      wlt = PyBtcWallet().readWalletFile(os.path.join(ARMORY_HOME_DIR, file))
  1211. +      print 'Loaded wallet %s (%s)' % (wlt.labelName, wlt.uniqueIDB58)
  1212. +      return (wlt.uniqueIDB58, wlt)
  1213. +
  1214. +   def run(self):
  1215. +      self.conn.open()
  1216. +      self.conn.heartbeat()
  1217. +      print 'Serial port open'
  1218. +
  1219. +   def extendWallet(self, wlt, index):
  1220. +      while wlt.lastComputedChainIndex < index:
  1221. +         wlt.computeNextAddress()
  1222. +
  1223. +   def unlockWallet(self, wlt):
  1224. +      # If the wallet is encrypted, get the passphrase
  1225. +      if wlt.useEncryption and wlt.isLocked:
  1226. +         for ntries in range(3):
  1227. +            self.writeInfo('Wallet passphrase required')
  1228. +            passwd = SecureBinaryData(getpass.getpass('Wallet Passphrase: '))
  1229. +            if wlt.verifyPassphrase(passwd):
  1230. +               self.writeInfo('Unlocking wallet')
  1231. +               break;
  1232. +            else:
  1233. +               self.writeError('Incorrect passphrase')
  1234. +
  1235. +            if ntries == 2:
  1236. +               self.writeError('Wallet could not be unlocked')
  1237. +               return
  1238. +
  1239. +         wlt.unlock(securePassphrase=passwd)
  1240. +         passwd.destroy()
  1241. +
  1242. +   def receiveMessage(self, msg):
  1243. +      if isinstance(msg, bool):
  1244. +         if msg:
  1245. +            print 'Online wallet connected'
  1246. +         else:
  1247. +            print 'Online wallet disconnected'
  1248. +      elif isinstance(msg, SignatureRequest):
  1249. +         wlt = self.wallets.get(msg.wallet.uniqueIDB58)
  1250. +         if wlt:
  1251. +            idx = msg.wallet.lastComputedChainIndex
  1252. +            if idx > wlt.lastComputedChainIndex:
  1253. +               self.extendWallet(wlt, idx)
  1254. +            self.signTxDistProposal(msg)
  1255. +         else:
  1256. +            self.writeError('Cannot find wallet with id %s' % msg.wallet.uniqueIDB58)
  1257. +      elif isinstance(msg, OnlineWalletRequest):
  1258. +         response = OnlineWalletResponse()
  1259. +         for wlt in self.wallets.itervalues():
  1260. +            if not msg.uniqueIDB58 or msg.uniqueIDB58 == wlt.uniqueIDB58:
  1261. +               wltmsg = response.wallets.add()
  1262. +               wltmsg.uniqueIDB58 = wlt.uniqueIDB58
  1263. +               wltmsg.labelName = wlt.labelName
  1264. +               wltmsg.labelDescr = wlt.labelDescr
  1265. +
  1266. +               if not msg.metadataOnly:
  1267. +                  copy = wlt.forkOnlineWallet('.temp_wallet')
  1268. +                  os.remove('.temp_wallet')
  1269. +                  packer = BinaryPacker()
  1270. +                  copy.packHeader(packer)
  1271. +                  data = packer.getBinaryString()
  1272. +                  for addr160,addrObj in copy.addrMap.iteritems():
  1273. +                     if not addr160=='ROOT':
  1274. +                        data += '\x00' + addr160 + addrObj.serialize()
  1275. +
  1276. +                     for hashVal,comment in copy.commentsMap.iteritems():
  1277. +                        twoByteLength = int_to_binary(len(comment), widthBytes=2)
  1278. +                        if len(hashVal)==20:
  1279. +                           typestr = int_to_binary(WLT_DATATYPE_ADDRCOMMENT)
  1280. +                           data += typestr + hashVal + twoByteLength + comment
  1281. +                        elif len(hashVal)==32:
  1282. +                           typestr = int_to_binary(WLT_DATATYPE_TXCOMMENT)
  1283. +                           data += typestr + hashVal + twoByteLength + comment
  1284. +
  1285. +                  wltmsg.packedBytes = zlib.compress(data)
  1286. +                  print 'Wallet %s size: %d bytes (flat), %d bytes (compressed)' % (wlt.uniqueIDB58, len(data), len(wltmsg.packedBytes))
  1287. +
  1288. +         self.conn.write(response)
  1289. +      else:
  1290. +         print msg.SerializeToString()
  1291. +
  1292. +   def signTxDistProposal(self, wlt, request):
  1293. +      try:
  1294. +         txdp = PyTxDistProposal().unserializeAscii(request.txDP)
  1295. +         print 'Received unsigned tx proposal %s' % txdp.uniqueB58
  1296. +
  1297. +         found = 0
  1298. +         for a160 in txdp.inAddr20Lists:
  1299. +            if wlt.hasAddr(a160[0]):
  1300. +               found += 1
  1301. +
  1302. +         if found == 0:
  1303. +            self.writeError('Unable to find any signing keys')
  1304. +            return
  1305. +         elif found < len(txdp.inAddr20Lists) and request.type == request.FULL:
  1306. +            self.writeError('Unable to find all signing keys')
  1307. +            return
  1308. +
  1309. +         self.unlockWallet(wlt)
  1310. +
  1311. +         try:
  1312. +            wlt.signTxDistProposal(txdp)
  1313. +            if not request.keepWalletUnlocked:
  1314. +               wlt.lock()
  1315. +
  1316. +            if not txdp.checkTxHasEnoughSignatures() and request.type == request.FULL:
  1317. +               self.writeError('Error signing transaction.  Most likely this is not the correct wallet')
  1318. +            else:
  1319. +               msg = SignatureResponse()
  1320. +               msg.txDP = txdp.serializeAscii()
  1321. +               self.conn.write(msg)
  1322. +               print 'Sent signed tx proposal'
  1323. +
  1324. +         except WalletLockError:
  1325. +            self.writeError('Wallet is somehow still locked')
  1326. +         except:
  1327. +            self.writeError('Unknown signing error')
  1328. +
  1329. +      except IndexError:
  1330. +         self.writeError('Invalid transaction distribution proposal')
  1331. +
  1332. +with OfflineSerialServer() as server:
  1333. +   server.run()
  1334. +   try:
  1335. +      while True:
  1336. +         time.sleep(1)
  1337. +   except KeyboardInterrupt:
  1338. +      pass
  1339. Index: armoryengine.py
  1340. IDEA additional info:
  1341. Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
  1342. <+>UTF-8
  1343. ===================================================================
  1344. --- armoryengine.py (revision e1b435821967518f46f5d1722bf6245e5e011e29)
  1345. +++ armoryengine.py (revision )
  1346. @@ -34,7 +34,7 @@
  1347.  ################################################################################
  1348.  
  1349.  
  1350. -# Version Numbers
  1351. +# Version Numbers
  1352.  BTCARMORY_VERSION    = (0, 86, 0, 0)  # (Major, Minor, Minor++, even-more-minor)
  1353.  PYBTCWALLET_VERSION  = (1, 35, 0, 0)  # (Major, Minor, Minor++, even-more-minor)
  1354.  
  1355. @@ -58,6 +58,9 @@
  1356.  import threading
  1357.  from struct import pack, unpack
  1358.  from datetime import datetime
  1359. +import serial
  1360. +import serial_pb2 as pb
  1361. +import uuid
  1362.  
  1363.  
  1364.  from sys import argv
  1365. @@ -7428,7 +7431,7 @@
  1366.  
  1367.  
  1368.     #############################################################################
  1369. -   def unpackHeader(self, binUnpacker):
  1370. +   def unpackHeader(self, binUnpacker, fileUpdate=True):
  1371.        """
  1372.        Unpacking the header information from a wallet file.  See the help text
  1373.        on the base class, PyBtcWallet, for more information on the wallet
  1374. @@ -7489,7 +7492,7 @@
  1375.        rawAddrData = binUnpacker.get(BINARY_CHUNK, self.pybtcaddrSize)
  1376.        self.addrMap['ROOT'] = PyBtcAddress().unserialize(rawAddrData)
  1377.        fixedAddrData = self.addrMap['ROOT'].serialize()
  1378. -      if not rawAddrData==fixedAddrData:
  1379. +      if fileUpdate and not rawAddrData==fixedAddrData:
  1380.           self.walletFileSafeUpdate([ \
  1381.              [WLT_UPDATE_MODIFY, self.offsetRootAddr, fixedAddrData]])
  1382.  
  1383. @@ -7555,8 +7558,16 @@
  1384.        wltdata = BinaryUnpacker(wltfile.read())
  1385.        wltfile.close()
  1386.  
  1387. +      TimerStop('readWalletFile')
  1388. +
  1389. +      return self.readWalletData(wltdata, doScanNow)
  1390. +
  1391. +   #############################################################################
  1392. +   def readWalletData(self, wltdata, doScanNow=False, fileUpdate=True):
  1393. +      TimerStart('readWalletData')
  1394. +
  1395.        self.cppWallet = Cpp.BtcWallet()
  1396. -      self.unpackHeader(wltdata)
  1397. +      self.unpackHeader(wltdata, fileUpdate=fileUpdate)
  1398.  
  1399.        self.lastComputedChainIndex = -UINT32_MAX
  1400.        self.lastComputedChainAddr160  = None
  1401. @@ -7569,7 +7580,7 @@
  1402.              newAddr.walletByteLoc = byteLocation + 21
  1403.              # Fix byte errors in the address data
  1404.              fixedAddrData = newAddr.serialize()
  1405. -            if not rawData==fixedAddrData:
  1406. +            if fileUpdate and not rawData==fixedAddrData:
  1407.                 self.walletFileSafeUpdate([ \
  1408.                    [WLT_UPDATE_MODIFY, newAddr.walletByteLoc, fixedAddrData]])
  1409.              if newAddr.useEncryption:
  1410. @@ -7608,7 +7619,7 @@
  1411.           LOGERROR('Wallets older than version 1.35 no loger supported!')
  1412.           return
  1413.  
  1414. -      TimerStop('readWalletFile')
  1415. +      TimerStop('readWalletData')
  1416.  
  1417.        return self
  1418.  
  1419. @@ -11441,14 +11452,112 @@
  1420.     f.write('\n\nNote: timings may be incorrect if errors '
  1421.                        'were triggered in the timed functions')
  1422.     print 'Saved timings to file: %s' % fname
  1423. -  
  1424. +
  1425.  
  1426. +class PySerialConnection(threading.Thread):
  1427. +   # number of seconds between heartbeats
  1428. +   HEARTBEAT_INTERVAL = 60
  1429.  
  1430. +   lastHeartbeat = 0
  1431. +   lastHeartbeatGuid = ''
  1432. +   isConnected = False
  1433. +   codeType = {
  1434. +      '0': pb.Heartbeat,
  1435. +      '1': pb.SignatureRequest,
  1436. +      '2': pb.SignatureResponse,
  1437. +      '3': pb.OnlineWalletRequest,
  1438. +      '4': pb.OnlineWalletResponse,
  1439. +      #'5': pb.InputRequest,
  1440. +      #'6': pb.InputResponse,
  1441. +      '5': pb.Notification,
  1442. +      '6': pb.Reset
  1443. +   }
  1444.  
  1445. +   def __init__(self, device, rate, callback):
  1446. +      self.port = serial.Serial(device, rate)
  1447. +      self.callback = callback
  1448. +      threading.Thread.__init__(self)
  1449.  
  1450. +   def __enter__(self):
  1451. +      return self
  1452.  
  1453. +   def __exit__(self, exc_type, exc_val, exc_tb):
  1454. +      self.close()
  1455.  
  1456. +   def open(self):
  1457. +      self.isOpen = True
  1458. +      if not self.port.isOpen():
  1459. +         self.port.open()
  1460. +      if not self.isAlive():
  1461. +         self.start()
  1462.  
  1463. +   def close(self):
  1464. +      self.isOpen = False
  1465. +      if self.port.isOpen():
  1466. +         self.port.close()
  1467.  
  1468. +   def heartbeat(self, guid=str(uuid.uuid1())):
  1469. +      if guid != self.lastHeartbeatGuid:
  1470. +         self.lastHeartbeatGuid = guid;
  1471. +         beat = pb.Heartbeat()
  1472. +         beat.guid = guid
  1473. +         self.write(beat)
  1474. +         return True
  1475. +      return False
  1476.  
  1477. +   def setConnected(self, connected):
  1478. +      if connected != self.isConnected:
  1479. +         self.callback(connected)
  1480. +         self.isConnected = connected
  1481.  
  1482. +   def write(self, message):
  1483. +      with threading.Lock() as lock:
  1484. +         code = (code for code, type in self.codeType.items() if isinstance(message, type)).next()
  1485. +         serialized = message.SerializeToString()
  1486. +         packed_len = pack('!I', len(serialized))
  1487. +         self.port.write(code + packed_len + serialized)
  1488. +         self.port.flush()
  1489. +
  1490. +   def _read(self):
  1491. +      code = self._read_n_bytes(1)
  1492. +      len_buf = self._read_n_bytes(4)
  1493. +      msg_len = unpack('!I', len_buf)[0]
  1494. +      msg_buf = self._read_n_bytes(msg_len)
  1495. +
  1496. +      msg = self.codeType[code]()
  1497. +      msg.ParseFromString(msg_buf)
  1498. +      return msg
  1499. +
  1500. +   def _read_n_bytes(self, n):
  1501. +      buf = ''
  1502. +      try:
  1503. +         while n > 0:
  1504. +            data = self.port.read(n)
  1505. +            if data == '':
  1506. +               raise RuntimeError('unexpected connection close')
  1507. +            buf += data
  1508. +            n -= len(data)
  1509. +         return buf
  1510. +      except OverflowError:
  1511. +         print 'Failed to read %d bytes. Waiting: %s' % (n, self.port.read(self.port.inWaiting()))
  1512. +         pass
  1513. +
  1514. +   def run(self):
  1515. +      while self.isOpen:
  1516. +         beat = False
  1517. +         if self.port.inWaiting():
  1518. +            msg = self._read()
  1519. +            print 'Received serial message: %s' % msg.SerializeToString()
  1520. +            if isinstance(msg, pb.Heartbeat):
  1521. +               beat = self.heartbeat(msg.guid)
  1522. +               self.setConnected(True)
  1523. +               self.lastHeartbeat = time.time()
  1524. +
  1525. +            self.callback(msg)
  1526. +
  1527. +         if not beat:
  1528. +            self.setConnected(time.time() - self.lastHeartbeat < self.HEARTBEAT_INTERVAL)
  1529. +            if time.time() - self.lastHeartbeat > self.HEARTBEAT_INTERVAL / 2:
  1530. +               self.heartbeat()
  1531. +
  1532. +         time.sleep(1)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement