Repository
https://gist.github.com/spiiin/d6b1ec3dca17fbbedf6c03856cb48634
What Will I Learn?
In brief, write details of what the user is going to learn in a bullet list.
- You will learn how to use steemmonster library for retrive information about cards
- You will learn how to use some functions from python itertools/hashlib standard libraries
- And you will learn that a problem may have a non-standard solution
Requirements
- Basic understand of python
Difficulty
- Basic
Tutorial Contents
You must know about Steem Monsters, a decentralized, digital collectible trading card game built on the Steem blockchain. I buy started pack a long time ago but started to play just several days ago. There is much information about this game and some players creating contests with cards as prizes.
I think contests and prizes are cool. For example, there is one of the contest from (it's ended now, I waited for it to post the tutorial and not ruin contest).
The author wants to create a lottery - he chooses three random gold cards from his collection, and encrypt card names to fix his choice to the participants. I think, that a lottery is cool, but a programming puzzle will be even better, so I try to check if I can collect some info about cards.
At first, I need to collect all card names. There are currently 97 cards in game (74 beta, 4 promo, and 19 rewards cards)
It will take a long time to do so manually - card names only printed on cards as images in Steem Monsters' Market section, so it even not can be copied as text.
I started to search card list, but there is no available. I only found this article, but it is incomplete - only 11 water cards listed there. Next I found nice steemmonsters python library from .
It can be installed with standard python pip installer:
pip install steemmonsters
Library has shell-like interface, but we will use it as python module for writing our script.
We can get details for all cards with this script:
#import stemmonsters library
import steemmonsters.api
#create api class
api = steemmonsters.api.Api()
#request to api - get info about cards
cards_details = api.get_card_details()
#debug print of card info
for cd in cards_details:
print(cd)
Example of output (python dictionary):
{'id': 1, 'name': 'Goblin Shaman', 'color': 'Red', 'type': 'Monster', 'sub_type': None, 'rarity': 1, 'drop_rate': 80, 'stats': {'mana': [3, 3, 3, 3, 3, 3, 3, 3, 3, 3], 'attack': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'ranged': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'magic': [0, 0, 1, 1, 1, 1, 1, 1, 1, 1], 'armor': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'health': [4, 4, 3, 4, 3, 3, 3, 4, 5, 6], 'speed': [2, 3, 2, 2, 2, 3, 4, 4, 4, 4], 'abilities': [['Weaken'], [], [], [], ['Slow'], [], [], [], [], []]}, 'is_starter': False, 'editions': '0,1', 'created_block_num': None, 'last_update_tx': None}
The name field is exactly what are we want.
Next, as we can read in description of the contest, author mention, that he encrypt one rare card and two common. So, we can filter common and rare card names:
#filter for common and rare cards
common = [(c.get("name")) for c in cards_details if c.get("rarity")==1]
rare = [(c.get("name")) for c in cards_details if c.get("rarity")==2]
print("Card names recieved. Total: %d common cards and %d rare cards)"%(len(common), len(rare)))
This lines will print:
Card names recieved. Total: 33 common cards and 29 rare cards
At this point, I look on hash string, posted by author:
And think: if it is sha256 hash of the string "Card Name1, Card Name2, Card Name3" how many combinations I need to check for found string, that will match to hash?
sha256 is standard function for hashing, it used in bitcoin, steem and other crypto key-pairs generation.
It is not so many for bruteforce with computer.
Card Name1 can be replaced with 62 card names (any common and rare card).
Card Name2 can be replaced with 61 card names (any, except of Card Name1, it must be different card)
Card Name3 can be replaced with 60 card names (any, except of Card Name1 and Card Name2).
So, total we have 62 x 61 x 60 = 226920 permutations.
We can implement brute force search for all the permutations with this python code:
from itertools import permutations
import hashlib
#fill list of card names
cards_names = common + rare
#convert string to sha245 and check if hash is equal to answer
def checkHash(myStr):
m = hashlib.sha256()
m.update(myStr.encode('utf-8'))
return m.hexdigest() == "8iu7O23u9SRnkdjckjh29cZ3E9pOpCXJhwfdQjCM3bohrSfbo9541122GnXI9w8J"
#generate all permutations for card_names
all_permuations = permutations(cards_names, 3)
for i, p in enumerate(all_permuations):
#join list elements to string with ", " as separator
s = ", ".join(p)
#print debug results for every 1000th value to view progress
if i % 1000 == 0:
print(i)
print(s)
#if we found answer, print it and break search
if (checkHash(s)):
print(s)
break
It needs less than 1 second to check all variants.
But, it not found answer :(
I tried other variants:
CardName1, CardName2, CardName3 (with no spaces in card names)
Card Name1 Card Name 2 Card Name3 (with spaces and other symbols as word separator)
CARD NAME1, CARD NAME2, CARD NAME3 (with upper and lowercase...)
Card Name1\nCard Name2\nCard Name3 (with new lines in windows and unix style...)
and others
When you solving real tasks, not challenge, it's normal to not found obvious solution.
It's possible, that author of the contests missprint on card name, or just used "salt" - some additional string, that will change all hash. For example, hash of string
"My cards are: Card Name1, Card Name2, Card Name 3" will be totally different.
Next, I re-read the post and see this:
It's nick of author in steem-monsters discord channel.
I sent message to him with a hint's request, but he didn't answer me.
Looking ahead, author encrypt string without error, but he used not SHA256, but AES encryption algorithm with additional protection key, so brute force wouldn't work anyway.
But nickname of the author on discord () also is his steem/steem monsters nickname! IT IS A HINT! All cards stored in blockchain and we can see, what cards someone has.
Gold cards are really rare, so player can't has many of them (and it's extra cool for the author of the contest to give away as many as 3 cards!)
And the last piece of code to print all common and rare gold cards for certain player:
#print card name and it's rarity
def getCardInfo(p):
#search, if card_detail_id for our card is found is cards_details dictionary
for cd in cards_details:
if cd.get("id") == p.get("card_detail_id"):
#if card_detail_id matches, return card name and rarity
return (cd.get("name"), cd.get("rarity"))
#read full card collection for user
pd = api.get_collection("nomadic-maverick")
for p in pd.get("cards"):
#if we found gold card, print it's name and rarity
if p.get("gold") == True:
print(getCardInfo(p))
Output:
('Rexxie', 1)
('Silvershield Warrior', 1)
('Minotaur Warrior', 1)
('Clay Golem', 2)
('Silvershield Knight', 1)
('Highland Archer', 1)
('Divine Healer', 1)
('Rusty Android', 1)
('Rusty Android', 1)
('Grumpy Dwarf', 1)
('Flame Imp', 2)
I didn't format output, first element it tuple is card name, and second element is rarity code - 1 is common card, and 2 is rare card. It's only 3 rare cards in player's collection, so now you have 33% chance to win (or even 66% if you resteem contest's post).
I used my test account (created with help of
for my friend and not used before) for check my guess, and win this card!
Later I found other way to check collection of cards for other player - steempeak app already provide interface for it:
https://monsters.steempeak.com/@nomadic-maverick/collection
Proof of Work Done
https://gist.github.com/spiiin/d6b1ec3dca17fbbedf6c03856cb48634
Gold Medusa in my collection:
https://monsters.steempeak.com/@pinkwonder/collection
update:
Card goes to
https://monsters.steempeak.com/@monsterstamer/collection
because I have no water summoner yet =)
You can contact me on discord:
spiiin#1717