mirror of
https://git.wownero.com/lza_menace/wownero-python.git
synced 2024-08-15 03:25:25 +00:00
Squashed commit of the following:
commit 091aa668e194489e3a45e418941ea922c09f7024 Author: lance allen <lrallen03@gmail.com> Date: Mon Feb 19 09:32:50 2018 -0800 removing extraneous import commit 47d2dbcf4e20a4cbba1bb77caa39e8005db00042 Author: lance allen <lrallen03@gmail.com> Date: Mon Feb 19 00:18:19 2018 -0800 matching the version to emesik master and fixing the method name commit 63b5f5fae3a3125b36a6ed18f3f7e47e54cd1d1d Author: lance allen <lrallen03@gmail.com> Date: Sun Feb 18 22:38:37 2018 -0800 adding unit file for seed class commit 515ccfe18663d6f52810d59ccdd9ab51e2be7d58 Author: lance allen <lrallen03@gmail.com> Date: Sun Feb 18 22:38:17 2018 -0800 updating init files commit 6e0803661f08694a5a81a83a7fbeede6e18f741c Author: lance allen <lrallen03@gmail.com> Date: Sun Feb 18 22:37:46 2018 -0800 updating init from assert to raise, set gte operator on checks commit e066039bf8ccf5128987e6d291864f3a9d61df28 Author: lance allen <lrallen03@gmail.com> Date: Sun Feb 18 18:42:30 2018 -0800 move get_checksum outside of class and always return seed with checksum commit 1b0cb8adc6945d777abba18acfcaf8d3b6f4be42 Author: lance allen <lrallen03@gmail.com> Date: Sun Feb 18 17:14:46 2018 -0800 removing extraneous seed_ prefixes from class vars and change class param to commit 9b60a34da554696f3cfbfd7cb3f0780dfc32bc55 Author: lance allen <lrallen03@gmail.com> Date: Sun Feb 18 13:52:12 2018 -0800 move generate_hex function outside of class commit 162036cb38ff5a90cf515fd951d70299117866aa Author: lance allen <lrallen03@gmail.com> Date: Sun Feb 18 13:49:46 2018 -0800 adding string input to class constructor and determine validity commit 30f04ade1819b152fd600eff0b7364d9bd7aaab4 Author: lalanza808 <lrallen03@gmail.com> Date: Sat Feb 17 23:59:19 2018 -0800 adding commit hashes and revamping comments commit c46029fbc9bf74bf64231a1d4c4c6613be08bf00 Author: lalanza808 <lrallen03@gmail.com> Date: Sat Feb 17 23:18:07 2018 -0800 adding licensing, verb noun functions, checksum val, hex generation commit fa38d8aa1ea3aeb5ba7d03f89f9610a1b9b76e49 Author: lance allen <lrallen03@gmail.com> Date: Sat Feb 17 16:57:19 2018 -0800 adding seed class for encoding/decoding mnemonic phrases or hex strings commit d523e51a92c626a435aad64740d2fe04eb2f8b99 Author: lance allen <lrallen03@gmail.com> Date: Sat Feb 17 16:57:03 2018 -0800 adding english wordlist for mnemonic seeds commit f6d78a497a0d48b565f1a584b51150a03f9df7d6 Author: lance allen <lrallen03@gmail.com> Date: Sat Feb 17 16:56:48 2018 -0800 updating monero init file to include wordlists
This commit is contained in:
parent
9e9b75a42e
commit
6adb4eeae1
6 changed files with 1897 additions and 2 deletions
|
@ -1,3 +1,3 @@
|
|||
from . import address, account, daemon, wallet, numbers, prio
|
||||
from . import address, account, daemon, wallet, numbers, prio, wordlists, seed
|
||||
|
||||
__version__ = '0.1.1'
|
||||
__version__ = '0.1.2'
|
||||
|
|
168
monero/seed.py
Normal file
168
monero/seed.py
Normal file
|
@ -0,0 +1,168 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Electrum - lightweight Bitcoin client
|
||||
# Copyright (C) 2011 thomasv@gitorious
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
#
|
||||
# Note about US patent no 5892470: Here each word does not represent a given digit.
|
||||
# Instead, the digit represented by a word is variable, it depends on the previous word.
|
||||
#
|
||||
# Copied 17 February 2018 from MoneroPy, originally from Electrum:
|
||||
# https://github.com/bigreddmachine/MoneroPy/blob/master/moneropy/mnemonic.py ch: 80cc16c39b16c55a8d052fbf7fae68644f7a5f02
|
||||
# https://github.com/spesmilo/electrum/blob/master/lib/old_mnemonic.py ch:9a0aa9b4783ea03ea13c6d668e080e0cdf261c5b
|
||||
|
||||
from monero import wordlists
|
||||
from binascii import crc32, hexlify
|
||||
from os import urandom
|
||||
|
||||
class Seed(object):
|
||||
"""Creates a seed object either from local system randomness or an imported phrase.
|
||||
|
||||
:rtype: :class:`Seed <monero.seed.Seed>`
|
||||
"""
|
||||
|
||||
n = 1626
|
||||
wordlist = wordlists.english.wordlist # default english for now
|
||||
|
||||
phrase = "" #13 or 25 word mnemonic word string
|
||||
hex = "" # hexadecimal
|
||||
|
||||
def __init__(self, phrase=""):
|
||||
"""If user supplied a seed string to the class, break it down and determine
|
||||
if it's hexadecimal or mnemonic word string. Gather the values and store them.
|
||||
If no seed is passed, automatically generate a new one from local system randomness.
|
||||
|
||||
:rtype: :class:`Seed <monero.seed.Seed>`
|
||||
"""
|
||||
if phrase:
|
||||
seed_split = phrase.split(" ")
|
||||
if len(seed_split) >= 24:
|
||||
# standard mnemonic
|
||||
self.phrase = phrase
|
||||
if len(seed_split) == 25:
|
||||
# with checksum
|
||||
self.validate_checksum()
|
||||
self.hex = self.decode_seed()
|
||||
elif len(seed_split) >= 12:
|
||||
# mymonero mnemonic
|
||||
self.phrase = phrase
|
||||
if len(seed_split) == 13:
|
||||
# with checksum
|
||||
self.validate_checksum()
|
||||
self.hex = self.decode_seed()
|
||||
elif len(seed_split) == 1:
|
||||
# single string, probably hex, but confirm
|
||||
if not len(phrase) % 8 == 0:
|
||||
raise ValueError("Not valid hexadecimal: {hex}".format(hex=phrase))
|
||||
self.hex = phrase
|
||||
self.phrase = self.encode_seed()
|
||||
else:
|
||||
raise ValueError("Not valid mnemonic phrase: {phrase}".format(phrase=phrase))
|
||||
else:
|
||||
self.hex = generate_hex()
|
||||
self.encode_seed()
|
||||
|
||||
|
||||
def endian_swap(self, word):
|
||||
"""Given any string, swap bits and return the result.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
return "".join([word[i:i+2] for i in [6, 4, 2, 0]])
|
||||
|
||||
def encode_seed(self):
|
||||
"""Given a hexadecimal string, return it's mnemonic word representation with checksum.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
assert self.hex, "Seed hex not set"
|
||||
assert len(self.hex) % 8 == 0, "Not valid hexadecimal"
|
||||
out = []
|
||||
for i in range(len(self.hex) // 8):
|
||||
word = self.endian_swap(self.hex[8*i:8*i+8])
|
||||
x = int(word, 16)
|
||||
w1 = x % self.n
|
||||
w2 = (x // self.n + w1) % self.n
|
||||
w3 = (x // self.n // self.n + w2) % self.n
|
||||
out += [self.wordlist[w1], self.wordlist[w2], self.wordlist[w3]]
|
||||
checksum = get_checksum(" ".join(out))
|
||||
out.append(checksum)
|
||||
self.phrase = " ".join(out)
|
||||
return self.phrase
|
||||
|
||||
def decode_seed(self):
|
||||
"""Given a mnemonic word string, return it's hexadecimal representation.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
assert self.phrase, "Seed phrase not set"
|
||||
phrase = self.phrase.split(" ")
|
||||
assert len(phrase) >= 12, "Not valid mnemonic phrase"
|
||||
out = ""
|
||||
for i in range(len(phrase) // 3):
|
||||
word1, word2, word3 = phrase[3*i:3*i+3]
|
||||
w1 = self.wordlist.index(word1)
|
||||
w2 = self.wordlist.index(word2) % self.n
|
||||
w3 = self.wordlist.index(word3) % self.n
|
||||
x = w1 + self.n *((w2 - w1) % self.n) + self.n * self.n * ((w3 - w2) % self.n)
|
||||
out += self.endian_swap("%08x" % x)
|
||||
self.hex = out
|
||||
return self.hex
|
||||
|
||||
def validate_checksum(self):
|
||||
"""Given a mnemonic word string, confirm seed checksum (last word) matches the computed checksum.
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
assert self.phrase, "Seed phrase not set"
|
||||
phrase = self.phrase.split(" ")
|
||||
assert len(phrase) > 12, "Not valid mnemonic phrase"
|
||||
is_match = get_checksum(self.phrase) == phrase[-1]
|
||||
assert is_match, "Not valid checksum"
|
||||
return is_match
|
||||
|
||||
|
||||
def get_checksum(phrase):
|
||||
"""Given a mnemonic word string, return a string of the computed checksum.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
phrase_split = phrase.split(" ")
|
||||
assert len(phrase_split) >= 12, "Not valid mnemonic phrase"
|
||||
if len(phrase_split) > 13:
|
||||
# Standard format
|
||||
phrase = phrase_split[:24]
|
||||
else:
|
||||
# MyMonero format
|
||||
phrase = phrase_split[:12]
|
||||
wstr = "".join(word[:3] for word in phrase)
|
||||
z = ((crc32(wstr.encode()) & 0xffffffff) ^ 0xffffffff ) >> 0
|
||||
z2 = ((z ^ 0xffffffff) >> 0) % len(phrase)
|
||||
return phrase_split[z2]
|
||||
|
||||
def generate_hex(n_bytes=32):
|
||||
"""Generate a secure and random hexadecimal string. 32 bytes by default, but arguments can override.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
h = hexlify(urandom(n_bytes))
|
||||
return "".join(h.decode("utf-8"))
|
1
monero/wordlists/__init__.py
Normal file
1
monero/wordlists/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from . import english
|
1660
monero/wordlists/english.py
Normal file
1660
monero/wordlists/english.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -3,3 +3,4 @@ from . import test_numbers
|
|||
from . import test_transaction
|
||||
from . import test_wallet
|
||||
from . import test_jsonrpcwallet
|
||||
from . import test_seed
|
||||
|
|
65
tests/test_seed.py
Normal file
65
tests/test_seed.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import unittest
|
||||
from monero.seed import Seed, get_checksum
|
||||
|
||||
class SeedTestCase(unittest.TestCase):
|
||||
|
||||
def test_mnemonic_seed(self):
|
||||
# Known good 25 word seed phrases should construct a class, validate checksum, and register valid hex
|
||||
seed = Seed("wedge going quick racetrack auburn physics lectures light waist axes whipped habitat square awkward together injury niece nugget guarded hive obnoxious waxing faked folding square")
|
||||
self.assertTrue(seed.validate_checksum())
|
||||
self.assertEqual(seed.hex, "8ffa9f586b86d294d93731765d192765311bddc76a4fa60311f8af36bbf6fb06")
|
||||
|
||||
# Known good 24 word seed phrases should construct a class, store phrase with valid checksum, and register valid hex
|
||||
seed = Seed("wedge going quick racetrack auburn physics lectures light waist axes whipped habitat square awkward together injury niece nugget guarded hive obnoxious waxing faked folding")
|
||||
seed.encode_seed()
|
||||
self.assertTrue(seed.validate_checksum())
|
||||
self.assertEqual(seed.hex, "8ffa9f586b86d294d93731765d192765311bddc76a4fa60311f8af36bbf6fb06")
|
||||
|
||||
# Known good 25 word hexadecimal strings should construct a class, store phrase with valid checksum, and register valid hex
|
||||
seed = Seed("8ffa9f586b86d294d93731765d192765311bddc76a4fa60311f8af36bbf6fb06")
|
||||
self.assertTrue(seed.validate_checksum())
|
||||
self.assertEqual(seed.phrase, "wedge going quick racetrack auburn physics lectures light waist axes whipped habitat square awkward together injury niece nugget guarded hive obnoxious waxing faked folding square")
|
||||
|
||||
# Known good 13 word seed phrases should construct a class, validate checksum, and register valid hex
|
||||
seed = Seed("ought knowledge upright innocent eldest nerves gopher fowls below exquisite aces basin fowls")
|
||||
self.assertTrue(seed.validate_checksum())
|
||||
self.assertEqual(seed.hex, "932d70711acc2d536ca11fcb79e05516")
|
||||
|
||||
# Known good 12 word seed phrases should construct a class, store phrase with valid checksum, and register valid hex
|
||||
seed = Seed("ought knowledge upright innocent eldest nerves gopher fowls below exquisite aces basin")
|
||||
seed.encode_seed()
|
||||
self.assertTrue(seed.validate_checksum())
|
||||
self.assertEqual(seed.hex, "932d70711acc2d536ca11fcb79e05516")
|
||||
|
||||
# Known good 13 word hexadecimal strings should construct a class, store phrase with valid checksum, and register valid hex
|
||||
seed = Seed("932d70711acc2d536ca11fcb79e05516")
|
||||
self.assertTrue(seed.validate_checksum())
|
||||
self.assertEqual(seed.phrase, "ought knowledge upright innocent eldest nerves gopher fowls below exquisite aces basin fowls")
|
||||
|
||||
# Generated seed phrases should be 25 words, validate checksum, register valid hex, and return matching outputs for decode/encode
|
||||
seed = Seed()
|
||||
seed_split = seed.phrase.split(" ")
|
||||
self.assertTrue(len(seed_split) == 25)
|
||||
self.assertTrue(seed.validate_checksum())
|
||||
self.assertTrue(len(seed.hex) % 8 == 0)
|
||||
self.assertEqual(seed.hex, seed.decode_seed())
|
||||
self.assertEqual(seed.phrase, seed.encode_seed())
|
||||
|
||||
# Invalid phrases should not be allowed
|
||||
with self.assertRaises(ValueError) as ts:
|
||||
Seed("oh damn you thought fool")
|
||||
self.assertEqual(ts.expected, ValueError)
|
||||
with self.assertRaises(ValueError) as ts:
|
||||
Seed("Thi5isMyS3cr3tPa55w0rd")
|
||||
self.assertEqual(ts.expected, ValueError)
|
||||
with self.assertRaises(ValueError) as ts:
|
||||
Seed(" ")
|
||||
self.assertEqual(ts.expected, ValueError)
|
||||
with self.assertRaises(ValueError) as ts:
|
||||
Seed("\\x008")
|
||||
self.assertEqual(ts.expected, ValueError)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Loading…
Reference in a new issue