seed.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. #!/usr/bin/env python
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. seed.py: Seed-related classes and methods for the MMGen suite
  20. """
  21. import sys
  22. from binascii import hexlify,unhexlify
  23. import mmgen.config as g
  24. from mmgen.obj import *
  25. from mmgen.filename import *
  26. from mmgen.util import *
  27. from mmgen.bitcoin import b58encode_pad,b58decode_pad
  28. from mmgen.crypto import *
  29. pnm = g.proj_name
  30. class Seed(MMGenObject):
  31. def __init__(self,seed_bin=None):
  32. if not seed_bin:
  33. from mmgen.crypto import get_random
  34. # Truncate random data for smaller seed lengths
  35. seed_bin = sha256(get_random(1033)).digest()[:opt.seed_len/8]
  36. elif len(seed_bin)*8 not in g.seed_lens:
  37. die(3,"%s: invalid seed length" % len(seed_bin))
  38. self.data = seed_bin
  39. self.hexdata = hexlify(seed_bin)
  40. self.sid = make_chksum_8(seed_bin)
  41. self.len_bytes = len(seed_bin)
  42. self.len_bits = len(seed_bin) * 8
  43. class SeedSource(MMGenObject):
  44. class SeedSourceData(MMGenObject): pass
  45. desc = "seed source"
  46. seed_opts = {
  47. "mnemonic": "Mnemonic",
  48. "brain": "Brainwallet",
  49. "seed": "SeedFile",
  50. "incog": "IncogWallet",
  51. "incog_hex": "IncogWalletHex",
  52. "incog_hidden": "IncogWalletHidden",
  53. }
  54. def __init__(self,fn=None,seed=None,passwd=None):
  55. self.ssdata = self.SeedSourceData()
  56. if seed:
  57. self.desc = "new " + self.desc
  58. self.seed = seed
  59. self.ssdata.passwd = passwd
  60. self._pre_encode()
  61. self._encode()
  62. else:
  63. self._get_formatted_data(fn)
  64. self._deformat()
  65. self._decode()
  66. def _get_formatted_data(self,fn):
  67. if fn:
  68. self.infile = fn
  69. self.fmt_data = get_data_from_file(fn.name,self.desc)
  70. else:
  71. self.infile = None
  72. self.fmt_data = get_data_from_user(self.desc)
  73. def _pre_encode(self): pass
  74. def init(cls,fn=None,seed=None,passwd=None):
  75. sstype = None
  76. sopts=["%s_%s" % (l,k) for k in cls.seed_opts for l in "from","export"]
  77. for o in sopts:
  78. if o in opt.__dict__ and opt.__dict__[o]:
  79. sstype = cls.seed_opts[o.split("_",1)[1]]
  80. break
  81. if seed:
  82. return globals()[sstype or "Wallet"](seed=seed)
  83. else:
  84. if fn:
  85. if opt.from_incog_hidden:
  86. fn = Filename(fn,ftype="hincog")
  87. else:
  88. fn = Filename(fn)
  89. sstype = fn.linked_obj
  90. return globals()[sstype](fn=fn)
  91. else:
  92. return globals()[sstype or "Wallet"]()
  93. init = classmethod(init)
  94. def write_to_file(self):
  95. self._format()
  96. write_to_file_or_stdout(self._filename(),self.fmt_data, self.desc)
  97. class SeedSourceUnenc(SeedSource): pass
  98. class SeedSourceEnc(SeedSource):
  99. _ss_enc_msg = {
  100. 'choose_passphrase': """
  101. You must choose a passphrase to encrypt your new %s with.
  102. A key will be generated from your passphrase using a hash preset of '%s'.
  103. Please note that no strength checking of passphrases is performed. For an
  104. empty passphrase, just hit ENTER twice.
  105. """.strip()
  106. }
  107. def _pre_encode(self):
  108. if not self.ssdata.passwd:
  109. self._get_hash_preset()
  110. self._get_first_passwd()
  111. self._encrypt_seed()
  112. def _get_first_passwd(self):
  113. qmsg(self._ss_enc_msg['choose_passphrase'] % (self.desc,opt.hash_preset))
  114. self.ssdata.passwd = get_new_passphrase(what=self.desc)
  115. def _get_hash_preset(self):
  116. self.ssdata.hash_preset = \
  117. opt.hash_preset or get_hash_preset_from_user(what=self.desc)
  118. def _encrypt_seed(self):
  119. d = self.ssdata
  120. d.salt = sha256(get_random(128)).digest()[:g.salt_len]
  121. key = make_key(d.passwd, d.salt, d.hash_preset)
  122. d.key_id = make_chksum_8(key)
  123. d.enc_seed = encrypt_seed(self.seed.data,key)
  124. class Mnemonic (SeedSourceUnenc):
  125. desc = "mnemonic data"
  126. wl_checksums = {
  127. "electrum": '5ca31424',
  128. "tirosh": '1a5faeff'
  129. }
  130. mn_base = 1626
  131. wordlists = sorted(wl_checksums)
  132. def _mn2hex_pad(self,mn): return len(mn) * 8 / 3
  133. def _hex2mn_pad(self,hexnum): return len(hexnum) * 3 / 8
  134. def _baseNtohex(self,base,words,wl,pad=0):
  135. deconv = [wl.index(words[::-1][i])*(base**i)
  136. for i in range(len(words))]
  137. ret = ("{:0%sx}" % pad).format(sum(deconv))
  138. return "%s%s" % (('0' if len(ret) % 2 else ''), ret)
  139. def _hextobaseN(self,base,hexnum,wl,pad=0):
  140. num,ret = int(hexnum,16),[]
  141. while num:
  142. ret.append(num % base)
  143. num /= base
  144. return [wl[n] for n in [0] * (pad-len(ret)) + ret[::-1]]
  145. def _get_wordlist(self,wordlist=g.default_wordlist):
  146. wordlist = wordlist.lower()
  147. if wordlist not in self.wordlists:
  148. die(1,'"%s": invalid wordlist. Valid choices: %s' %
  149. (wordlist,'"'+'" "'.join(self.wordlists)+'"'))
  150. if wordlist == "electrum":
  151. from mmgen.mn_electrum import words
  152. elif wordlist == "tirosh":
  153. from mmgen.mn_tirosh import words
  154. else:
  155. die(3,"Internal error: unknown wordlist")
  156. return words.strip().split("\n")
  157. def _encode(self):
  158. wl = self._get_wordlist()
  159. seed_hex = hexlify(self.seed.data)
  160. mn = self._hextobaseN(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex))
  161. rev = self._baseNtohex(self.mn_base,mn,wl,self._mn2hex_pad(mn))
  162. if rev != seed_hex:
  163. msg("ERROR: seed recomputed from wordlist doesn't match original seed!")
  164. msg("Original seed: %s" % seed_hex)
  165. msg("Recomputed seed: %s" % rev)
  166. sys.exit(3)
  167. self.ssdata.mnemonic = mn
  168. def _format(self):
  169. self.fmt_data = " ".join(self.ssdata.mnemonic) + "\n"
  170. def _deformat(self):
  171. mn = self.fmt_data.split()
  172. wl = self._get_wordlist()
  173. if len(mn) not in g.mn_lens:
  174. die(3,"Invalid mnemonic (%i words). Allowed numbers of words: %s" %
  175. (len(mn),", ".join([str(i) for i in g.mn_lens])))
  176. for n,w in enumerate(mn,1):
  177. if w not in wl:
  178. die(3,"Invalid mnemonic: word #%s is not in the wordlist" % n)
  179. self.ssdata.mnemonic = mn
  180. def _decode(self):
  181. mn = self.ssdata.mnemonic
  182. wl = self._get_wordlist()
  183. seed_hex = self._baseNtohex(self.mn_base,mn,wl,self._mn2hex_pad(mn))
  184. rev = self._hextobaseN(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex))
  185. if rev != mn:
  186. msg("ERROR: mnemonic recomputed from seed not the same as original")
  187. die(3,"Recomputed mnemonic:\n%s" % " ".join(rev))
  188. qmsg("Valid mnemonic for seed ID %s" % make_chksum_8(unhexlify(seed_hex)))
  189. self.seed = Seed(unhexlify(seed_hex))
  190. def _filename(self):
  191. return "%s.%s" % (self.seed.sid, g.mn_ext)
  192. class SeedFile (SeedSourceUnenc):
  193. desc = "seed data"
  194. def _encode(self):
  195. b58seed = b58encode_pad(self.seed.data)
  196. self.ssdata.chksum = make_chksum_6(b58seed)
  197. self.ssdata.b58seed = b58seed
  198. def _decode(self):
  199. seed = b58decode_pad(self.ssdata.b58seed)
  200. if seed == False:
  201. msg("Invalid base 58 string: %s" % val)
  202. return False
  203. msg("Valid seed data for seed ID %s" % make_chksum_8(seed))
  204. self.seed = Seed(seed)
  205. def _format(self):
  206. self.fmt_data = "%s %s\n" % (
  207. self.ssdata.chksum,
  208. split_into_columns(4,self.ssdata.b58seed)
  209. )
  210. def _deformat(self):
  211. what = self.desc
  212. ld = self.fmt_data.split()
  213. if not (7 <= len(ld) <= 12): # 6 <= padded b58 data (ld[1:]) <= 11
  214. msg("Invalid data length (%s) in %s" % (len(ld),what))
  215. return False
  216. a,b = ld[0],"".join(ld[1:])
  217. if not is_chksum_6(a):
  218. msg("'%s': invalid checksum format, in %s" % (a, what))
  219. return False
  220. if not is_b58string(b):
  221. msg("'%s': not a base 58 string, in %s" % (b, what))
  222. return False
  223. vmsg_r("Validating %s checksum..." % what)
  224. compare_chksums(a,"checksum",make_chksum_6(b),"base 58 data")
  225. self.ssdata.chksum = a
  226. self.ssdata.b58seed = b
  227. def _filename(self):
  228. return "%s.%s" % (self.seed.sid, g.seed_ext)
  229. class Wallet (SeedSourceEnc):
  230. desc = "{pnm} wallet".format(pnm=pnm)
  231. def _encode(self):
  232. d = self.ssdata
  233. d.label = opt.label or "No Label"
  234. d.pw_status = "NE" if len(d.passwd) else "E"
  235. d.timestamp = make_timestamp()
  236. def _format(self):
  237. d = self.ssdata
  238. s = self.seed
  239. s_fmt = b58encode_pad(d.salt)
  240. es_fmt = b58encode_pad(d.enc_seed)
  241. lines = (
  242. d.label,
  243. "{} {} {} {} {}".format(s.sid.lower(), d.key_id.lower(),
  244. s.len_bits, d.pw_status, d.timestamp),
  245. "{}: {} {} {}".format(d.hash_preset,*get_hash_params(d.hash_preset)),
  246. "{} {}".format(make_chksum_6(s_fmt), split_into_columns(4,s_fmt)),
  247. "{} {}".format(make_chksum_6(es_fmt), split_into_columns(4,es_fmt))
  248. )
  249. chksum = make_chksum_6(" ".join(lines))
  250. self.fmt_data = "%s\n" % "\n".join((chksum,)+lines)
  251. def _decode(self):
  252. d = self.ssdata
  253. # Needed for multiple transactions with {}-txsign
  254. prompt_add = " "+self.infile.name if opt.quiet else ""
  255. passwd = get_mmgen_passphrase(self.desc+prompt_add)
  256. key = make_key(passwd, d.salt, d.hash_preset)
  257. self.seed = Seed(decrypt_seed(d.enc_seed, key, d.seed_id, d.key_id))
  258. def _check_master_chksum(self,lines):
  259. if len(lines) != 6:
  260. vmsg("Invalid number of lines (%s) in %s data" % (len(lines),self.desc))
  261. elif not is_chksum_6(lines[0]):
  262. vmsg("Incorrect Master checksum (%s) in %s data" % (lines[0],self.desc))
  263. else:
  264. chk = make_chksum_6(" ".join(lines[1:]))
  265. if compare_chksums(lines[0],"master wallet",chk,"computed"):
  266. return True
  267. msg("Invalid %s data" % self.desc)
  268. sys.exit(2)
  269. def _deformat(self):
  270. qmsg("Getting {pnm} wallet data from file '{f}'".format(
  271. pnm=pnm,f=self.infile.name))
  272. lines = self.fmt_data.rstrip().split("\n")
  273. self._check_master_chksum(lines)
  274. d = self.ssdata
  275. d.label = lines[1]
  276. d1,d2,d3,d4,d5 = lines[2].split()
  277. d.seed_id = d1.upper()
  278. d.key_id = d2.upper()
  279. d.seed_len = int(d3)
  280. d.pw_status,d.timestamp = d4,d5
  281. hpdata = lines[3].split()
  282. d.hash_preset = hpdata[0][:-1] # a string!
  283. hash_params = [int(i) for i in hpdata[1:]]
  284. if hash_params != get_hash_params(d.hash_preset):
  285. msg("Hash parameters '%s' don't match hash preset '%s'" %
  286. (" ".join(hash_params), d.hash_preset))
  287. sys.exit(3)
  288. for i,key in (4,"salt"),(5,"enc_seed"):
  289. l = lines[i].split(" ",1)
  290. if len(l) != 2:
  291. msg("Invalid format for %s in %s: %s" % (key,self.desc,val))
  292. sys.exit(3)
  293. chk,val = l[0],l[1].replace(" ","")
  294. compare_chksums(chk,"wallet "+key,
  295. make_chksum_6(val),"computed checksum")
  296. val_bin = b58decode_pad(val)
  297. if val_bin == False:
  298. msg("Invalid base 58 number: %s" % val)
  299. sys.exit(3)
  300. setattr(d,key,val_bin)
  301. def _filename(self):
  302. return "{}-{}[{},{}].{}".format(
  303. self.seed.sid,
  304. self.ssdata.key_id,
  305. self.seed.len_bits,
  306. self.ssdata.hash_preset,
  307. g.wallet_ext
  308. )
  309. # def __str__(self):
  310. ## label,metadata,hash_preset,salt,enc_seed):
  311. # d = self.ssdata
  312. # s = self.seed
  313. # out = ["WALLET DATA"]
  314. # fs = " {:18} {}"
  315. # pw_empty = "Yes" if d.metadata[3] == "E" else "No"
  316. # for i in (
  317. # ("Label:", d.label),
  318. # ("Seed ID:", s.sid),
  319. # ("Key ID:", d.key_id),
  320. # ("Seed length:", "%s bits (%s bytes)" % (s.len_bits,s.len_bytes)),
  321. # ("Scrypt params:", "Preset '%s' (%s)" % (opt.hash_preset,
  322. # " ".join([str(i) for i in get_hash_params(opt.hash_preset)])
  323. # )
  324. # ),
  325. # ("Passphrase empty?", pw_empty),
  326. # ("Timestamp:", "%s UTC" % d.metadata[4]),
  327. # ): out.append(fs.format(*i))
  328. #
  329. # fs = " {:6} {}"
  330. # for i in (
  331. # ("Salt:", ""),
  332. # (" b58:", b58encode_pad(d.salt)),
  333. # (" hex:", hexlify(d.salt)),
  334. # ("Encrypted seed:", ""),
  335. # (" b58:", b58encode_pad(d.enc_seed)),
  336. # (" hex:", hexlify(d.enc_seed))
  337. # ): out.append(fs.format(*i))
  338. #
  339. # return "\n".join(out)
  340. class Brainwallet (SeedSourceEnc):
  341. desc = "brainwallet"
  342. def _deformat(self):
  343. self.brainpasswd = " ".join(self.fmt_data.split())
  344. def _decode(self):
  345. self._get_hash_preset()
  346. vmsg_r("Hashing brainwallet data. Please wait...")
  347. # Use buflen arg of scrypt.hash() to get seed of desired length
  348. seed = scrypt_hash_passphrase(self.brainpasswd, "",
  349. self.ssdata.hash_preset, buflen=opt.seed_len/8)
  350. vmsg("Done")
  351. self.seed = Seed(seed)
  352. class IncogWallet (SeedSourceEnc):
  353. desc = "incognito wallet"
  354. _icg_msg = {
  355. 'incog_iv_id': """
  356. Check that the generated Incog ID above is correct. If it's not, then your
  357. incognito data is incorrect or corrupted.
  358. """.strip(),
  359. 'incog_iv_id_hidden': """
  360. Check that the generated Incog ID above is correct. If it's not, then your
  361. incognito data is incorrect or corrupted, or you've supplied an incorrect
  362. offset.
  363. """.strip(),
  364. 'incorrect_incog_passphrase_try_again': """
  365. Incorrect passphrase, hash preset, or maybe old-format incog wallet.
  366. Try again? (Y)es, (n)o, (m)ore information:
  367. """.strip(),
  368. 'confirm_seed_id': """
  369. If the seed ID above is correct but you're seeing this message, then you need
  370. to exit and re-run the program with the '--old-incog-fmt' option.
  371. """.strip(),
  372. }
  373. def _make_iv_chksum(self,s): return sha256(s).hexdigest()[:8].upper()
  374. def _get_incog_data_len(self,seed_len):
  375. return g.aesctr_iv_len + g.salt_len + g.hincog_chk_len + seed_len/8
  376. def _encode (self):
  377. d = self.ssdata
  378. # IV is used BOTH to initialize counter and to salt password!
  379. d.iv = get_random(g.aesctr_iv_len)
  380. d.iv_id = self._make_iv_chksum(d.iv)
  381. msg("Incog ID: %s" % d.iv_id)
  382. d.salt = get_random(g.salt_len)
  383. key = make_key(d.passwd, d.salt, d.hash_preset, "incog wallet key")
  384. chk = sha256(self.seed.data).digest()[:8]
  385. d.enc_seed = encrypt_data(chk + self.seed.data, key, 1, "seed")
  386. d.wrapper_key = make_key(d.passwd, d.iv, d.hash_preset, "incog wrapper key")
  387. d.key_id = make_chksum_8(d.wrapper_key)
  388. d.data_len = self._get_incog_data_len(opt.seed_len)
  389. def _format(self):
  390. d = self.ssdata
  391. self.fmt_data = d.iv + encrypt_data(
  392. d.salt + d.enc_seed,
  393. d.wrapper_key,
  394. int(hexlify(d.iv),16),
  395. "incog data"
  396. )
  397. def _filename(self):
  398. return "{}-{}-{}[{},{}].{}".format(
  399. self.seed.sid,
  400. self.ssdata.key_id,
  401. self.ssdata.iv_id,
  402. self.seed.len_bits,
  403. self.ssdata.hash_preset,
  404. g.incog_ext
  405. )
  406. def _deformat(self):
  407. # Data could be of invalid length, so check:
  408. valid_dlens = map(self._get_incog_data_len, g.seed_lens)
  409. # => [56, 64, 72]
  410. raw_d = self.fmt_data
  411. if len(raw_d) not in valid_dlens:
  412. die(1,
  413. "Invalid incognito file size: %s. Valid sizes (in bytes): %s" %
  414. (len(raw_d), " ".join(map(str, valid_dlens))))
  415. d = self.ssdata
  416. d.iv = raw_d[0:g.aesctr_iv_len]
  417. d.incog_id = self._make_iv_chksum(d.iv)
  418. d.enc_incog_data = raw_d[g.aesctr_iv_len:]
  419. msg("Incog ID: %s" % d.incog_id)
  420. qmsg("Check the applicable value against your records")
  421. k = 'incog_iv_id_hidden' if opt.from_incog_hidden else 'incog_iv_id'
  422. vmsg("\n%s\n" % self._icg_msg[k])
  423. def _decode(self):
  424. d = self.ssdata
  425. prompt_info="{pnm} incognito wallet".format(pnm=pnm)
  426. while True:
  427. passwd = get_mmgen_passphrase(prompt_info+" "+d.incog_id)
  428. qmsg("Configured hash presets: %s" %
  429. " ".join(sorted(g.hash_presets)))
  430. d.hash_preset = get_hash_preset_from_user(what="incog wallet")
  431. # IV is used BOTH to initialize counter and to salt password!
  432. key = make_key(passwd, d.iv, d.hash_preset, "wrapper key")
  433. dd = decrypt_data(d.enc_incog_data, key,
  434. int(hexlify(d.iv),16), "incog data")
  435. d.salt = dd[0:g.salt_len]
  436. d.enc_seed = dd[g.salt_len:]
  437. key = make_key(passwd, d.salt, d.hash_preset, "main key")
  438. vmsg("Key ID: %s" % make_chksum_8(key))
  439. ret = decrypt_seed(d.enc_seed, key, "", "")
  440. chk,seed_maybe = ret[:8],ret[8:]
  441. if sha256(seed_maybe).digest()[:8] == chk:
  442. msg("Passphrase and hash preset are correct")
  443. seed = seed_maybe
  444. break
  445. else:
  446. msg("Incorrect passphrase or hash preset")
  447. self.seed = Seed(seed)
  448. class IncogWalletHex (IncogWallet):
  449. def _deformat(self):
  450. self.fmt_data = decode_pretty_hexdump(self.fmt_data)
  451. IncogWallet._deformat(self)
  452. class IncogWalletHidden (IncogWallet):
  453. def _parse_hincog_opt(self):
  454. class HincogParams(MMGenObject): pass
  455. o = opt.from_incog_hidden or opt.export_incog_hidden
  456. p = HincogParams()
  457. a,b = o.split(",")
  458. p.filename = a
  459. p.offset = int(b)
  460. return p
  461. def _check_valid_offset(self,fn,action):
  462. d = self.ssdata
  463. if fn.size < d.hincog_offset + d.data_len:
  464. die(1,
  465. "Destination file has length %s, too short to %s %s bytes of data at offset %s"
  466. % (f.size,action,d.data_len,d.hincog_offset))
  467. # overrides method in SeedSource
  468. def _get_formatted_data(self,fn):
  469. if fn: die(1,
  470. "Specify the filename as a parameter of the '--from-hidden-incog' option")
  471. d = self.ssdata
  472. p = self._parse_hincog_opt()
  473. d.hincog_offset = p.offset
  474. self.infile = Filename(p.filename,ftype="hincog")
  475. qmsg("Getting hidden incog data from file '%s'" % self.infile.name)
  476. # Already sanity-checked:
  477. d.data_len = self._get_incog_data_len(opt.seed_len)
  478. self._check_valid_offset(self.infile,"read")
  479. import os
  480. fh = os.open(self.infile.name,os.O_RDONLY)
  481. os.lseek(fh,int(p.offset),os.SEEK_SET)
  482. self.fmt_data = os.read(fh,d.data_len)
  483. os.close(fh)
  484. qmsg("Data read from file '%s' at offset %s" %
  485. (self.infile.name,p.offset), "Data read from file")
  486. # overrides method in SeedSource
  487. def write_to_file(self):
  488. d = self.ssdata
  489. self._format()
  490. compare_or_die(d.data_len, "target data length",
  491. len(self.fmt_data),"length of formatted " + self.desc)
  492. p = self._parse_hincog_opt()
  493. d.hincog_offset = p.offset
  494. self.outfile = f = Filename(p.filename,ftype="hincog")
  495. if opt.debug:
  496. Msg("Incog data len %s, offset %s" % (d.data_len,p.offset))
  497. self._check_valid_offset(f,"write")
  498. if not opt.quiet: confirm_or_exit("","alter file '%s'" % f.name)
  499. import os
  500. fh = os.open(f.name,os.O_RDWR)
  501. os.lseek(fh, int(p.offset), os.SEEK_SET)
  502. os.write(fh, self.fmt_data)
  503. os.close(fh)
  504. msg("Data written to file '%s' at offset %s" % (f.name,p.offset))