patternpythonMinor
Produce bitcoin private key from 31 playing cards
Viewed 0 times
playingproducecardsprivatebitcoinfromkey
Problem
I've written some Python code to generate a random hexadecimal string using 31 playing cards drawn without replacement (so no card appears more than once). This is sufficient to provide more than 160 random bits, which is considered sufficient for a bitcoin private key.
I'm interested to know whether anything in the code or the way it is presented would make life difficult for a programmer using it elsewhere. I would like to hear ways that it can be improved in this specific respect - ease of re-use. Any other types of criticism are also welcome.
The full code is here and I include the main functional parts below (I've omitted the long doc string and exception classes).
What would make this more useful for other programmers?
```
from math import factorial
factorial52 = factorial(52)
upperLimit = factorial52//factorial(52-31) - 1
cardRanks = "A23456789TJQK"
cardSuits = "SHDC"
cardCharacters = cardRanks + cardSuits
hexCharacters = "0123456789ABCDEF"
recognisedCharacters = set(i for i in (cardCharacters + hexCharacters))
allCards = []
for s in cardSuits:
for t in cardRanks:
allCards.append(t+s)
def request_input():
print(
"\n"
"Enter a list of 31 playing cards in the format\n"
"AS 2H 3D 4C 5S 6H 7D 8C 9S TH JD QC KS ...\n"
"\n"
"or a hexadecimal number in the range\n"
"0 to 114882682E46B11EADE9F57C1E3E0BBD47FFFFFFF (52! / (52-31)! - 1)\n"
"\n"
"In either case you may include spaces or not as you wish.\n"
"Use T rather than 10. For example TH for ten of hearts.\n"
"Upper and lower case letters are equivalent.\n"
"\n"
)
return input()
def process(argument):
"""Convert the argument from cards to hex or hex to cards.
Decide whether the argument is hexadecimal or a list of cards.
Ambiguity is possible, as some of the characters used to
represent cards are valid hexadecimal characters. However, a
valid list of 31 cards will conta
I'm interested to know whether anything in the code or the way it is presented would make life difficult for a programmer using it elsewhere. I would like to hear ways that it can be improved in this specific respect - ease of re-use. Any other types of criticism are also welcome.
The full code is here and I include the main functional parts below (I've omitted the long doc string and exception classes).
What would make this more useful for other programmers?
```
from math import factorial
factorial52 = factorial(52)
upperLimit = factorial52//factorial(52-31) - 1
cardRanks = "A23456789TJQK"
cardSuits = "SHDC"
cardCharacters = cardRanks + cardSuits
hexCharacters = "0123456789ABCDEF"
recognisedCharacters = set(i for i in (cardCharacters + hexCharacters))
allCards = []
for s in cardSuits:
for t in cardRanks:
allCards.append(t+s)
def request_input():
print(
"\n"
"Enter a list of 31 playing cards in the format\n"
"AS 2H 3D 4C 5S 6H 7D 8C 9S TH JD QC KS ...\n"
"\n"
"or a hexadecimal number in the range\n"
"0 to 114882682E46B11EADE9F57C1E3E0BBD47FFFFFFF (52! / (52-31)! - 1)\n"
"\n"
"In either case you may include spaces or not as you wish.\n"
"Use T rather than 10. For example TH for ten of hearts.\n"
"Upper and lower case letters are equivalent.\n"
"\n"
)
return input()
def process(argument):
"""Convert the argument from cards to hex or hex to cards.
Decide whether the argument is hexadecimal or a list of cards.
Ambiguity is possible, as some of the characters used to
represent cards are valid hexadecimal characters. However, a
valid list of 31 cards will conta
Solution
from math import factorial
factorial52 = factorial(52)Why do this? Its probably better to just call
factorial(52) in the next lineupperLimit = factorial52//factorial(52-31) - 1The python convention is make constants in ALL_CAPS
cardRanks = "A23456789TJQK"
cardSuits = "SHDC"
cardCharacters = cardRanks + cardSuits
hexCharacters = "0123456789ABCDEF"
recognisedCharacters = set(i for i in (cardCharacters + hexCharacters))This is the same as
set(cardCharacters + hexCharacters) except slower.allCards = []
for s in cardSuits:
for t in cardRanks:
allCards.append(t+s)You can use
allCards = list(itertools.product(cardSuits, cardRanks)). def request_input():
print(
"\n"
"Enter a list of 31 playing cards in the format\n"
"AS 2H 3D 4C 5S 6H 7D 8C 9S TH JD QC KS ...\n"
"\n"
"or a hexadecimal number in the range\n"
"0 to 114882682E46B11EADE9F57C1E3E0BBD47FFFFFFF (52! / (52-31)! - 1)\n"
"\n"
"In either case you may include spaces or not as you wish.\n"
"Use T rather than 10. For example TH for ten of hearts.\n"
"Upper and lower case letters are equivalent.\n"
"\n"
)
return input()
def process(argument):process and argument are both very generic names. Its better to have a more informative name."""Convert the argument from cards to hex or hex to cards.
Decide whether the argument is hexadecimal or a list of cards.
Ambiguity is possible, as some of the characters used to
represent cards are valid hexadecimal characters. However, a
valid list of 31 cards will contain no duplicate cards, and will
therefore contain some hearts or spades, represented by H or S.
A valid list of 31 cards will therefore never be a valid
hexadecimal string.
"""
cleanArgument = nonwhitespace(argument).upper()
check_for_unrecognised_characters(cleanArgument)Does this really help you? Can you get away with just checking for valid hex or valid cards?
try:
value = int(cleanArgument, 16) # Gives error if not hex.
except ValueError:
print(convert_to_hex(cleanArgument))
else:
print(convert_to_cards(value))
def nonwhitespace(argument):
"""Return argument with all whitespace removed.
This includes removing any single spaces within the string.
"""
return "".join(argument.split())That's not a very efficient way to do that. Instead, I'd suggest using a regex replace to find all the whitespace and remove it.
def check_for_unrecognised_characters(argument):
"""Raise an exception if a character is unrecognised.A "check" function should probably return True or False to indicate the value checked. An exception should indicate that the function failed to accomplish what was asked. In terms of checking, its not failure to have that be check came up false.
The only recognised characters in this context are hexadecimal
characters and card ranks and suits.
"""
for i in argument:i is bad name. Firstly, I'd suggest avoiding single letter variable names, they are hard to determine what they are. Also, i usually is taken to stand for index, and this isn't an index.if i not in recognisedCharacters:
message = ("Character '" + i +
"' not recognised as part of card or hexadecimal.")
raise UnrecognisedCharacterError(message)I suggest using
repr(i) to add the quotes. Also look at string formatting rather than concatenating strings.def convert_to_hex(argument):
check_number_of_characters(argument)
listOfCards = [argument[i:i+2] for i in range(0, 62, 2)]
check_if_cards(listOfCards)
check_for_card_repetition(listOfCards)
return hex_representation(listOfCards)I'd suggest that you really want something like:
cards = string_to_cards(text_input) # throws exception if not valid cards
return cards_to_hex(cards)Whereas this function mixes the idea of reading the card representation with it overall logic.
def convert_to_cards(value):
check_hex_is_in_range(value)
return corresponding_cards(value)
def check_number_of_characters(argument):The function name is confusing, because it gives no hints about what character limit is going on.
```
"""Raise an exception if not exactly 31 cards."""
length = len(argument)
if length 62:
message = (
"31 cards required, each 2 characters.\n"
"62 characters required in total.\n"
"" + str(length) + " nonwhitespace characters provided."
)
raise TooManyCardsError(message)
def check_if_cards(listOfCards):
"""Raise an exception if not valid cards.
Every card should be a rank character followed by a suit character.
"""
for i in listOfCards:
if i[0] not in cardRanks:
message = (
"'"
Code Snippets
from math import factorial
factorial52 = factorial(52)upperLimit = factorial52//factorial(52-31) - 1cardRanks = "A23456789TJQK"
cardSuits = "SHDC"
cardCharacters = cardRanks + cardSuits
hexCharacters = "0123456789ABCDEF"
recognisedCharacters = set(i for i in (cardCharacters + hexCharacters))allCards = []
for s in cardSuits:
for t in cardRanks:
allCards.append(t+s)def request_input():
print(
"\n"
"Enter a list of 31 playing cards in the format\n"
"AS 2H 3D 4C 5S 6H 7D 8C 9S TH JD QC KS ...\n"
"\n"
"or a hexadecimal number in the range\n"
"0 to 114882682E46B11EADE9F57C1E3E0BBD47FFFFFFF (52! / (52-31)! - 1)\n"
"\n"
"In either case you may include spaces or not as you wish.\n"
"Use T rather than 10. For example TH for ten of hearts.\n"
"Upper and lower case letters are equivalent.\n"
"\n"
)
return input()
def process(argument):Context
StackExchange Code Review Q#42526, answer score: 6
Revisions (0)
No revisions yet.