diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..db4561ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ diff --git a/README.md b/README.md new file mode 100644 index 00000000..dd53bb0f --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# What is this? + +Python library for Graphene 2.0! + +For Graphene 0.x please see `python-graphenerpc` and `python-graphenetools`. + +# Installtion + + python3 setup.py install --user + +# Documentation + +Documentation is written with the help of `sphinx` and can be compile to `html` +with: + + cd docs + make html + +Alternatively, thanks to readthedocs.org, the documentation can be viewed at: + + * http://python-graphenelib.readthedocs.org/en/latest/ diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..1a3197b3 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,192 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-graphenelib.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-graphenelib.qhc" + +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/python-graphenelib" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-graphenelib" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/address.rst b/docs/address.rst new file mode 100644 index 00000000..baf1891f --- /dev/null +++ b/docs/address.rst @@ -0,0 +1,107 @@ +Address Module +============== + +Requirements +------------ +* python-ecdsa + +Usage +----- + +.. note:: Everthing returned by any method of this module is an instance of the + :doc:`base58` + +This module contains the following classes: + +``Address`` +^^^^^^^^^^^ +Derives addresses of ``PublicKeys`` and represents it according to +``Base58CheckEncode`` or ``GrapheneBase58CheckEncode``. + +* ``bytes(Address)`` + Returns the raw content of the ``GrapheneBase58CheckEncoded`` address +* ``str(Address)`` + Returns the readable Graphene address. This call is equivalent to ``format(Address, "BTS")`` +* ``repr(Address)`` + Gives the hex representation of the ``GrapheneBase58CheckEncoded`` Graphene address. +* ``format(Address,"btc")`` + Uses the raw address/public key to derive a valid Bitcoin address. +* ``format(Address,"*")`` + May be issued to get valid "MUSIC", "PLAY" or any other Graphene compatible + address with corresponding prefix. + +Example::: + + Address("BTSFN9r6VYzBK8EKtMewfNbfiGCr56pHDBFi") + +``PublicKey`` +^^^^^^^^^^^^^ +Inherits ``Address``. + +* ``bytes(PublicKey)`` + Returns the raw public key +* ``str(PublicKey)`` + Returns the readable Graphene public key. This call is equivalent to ``format(PublicKey, "BTS")`` +* ``repr(PublicKey)`` + Gives the hex representation of the Graphene public key. +* ``format(PublicKey,_format)`` + Formats the instance of :doc:`Base58 ` according to ``_format`` + +Example::: + + PublicKey("BTS6UtYWWs3rkZGV8JA86qrgkG6tyFksgECefKE1MiH4HkLD8PFGL") + +``PrivateKey`` +^^^^^^^^^^^^^^ +Inherits ``PublicKey``. Derives the compressed and uncompressed public keys and +constructs two instances of ``PublicKey`` + +* ``bytes(PrivateKey)`` + Returns the raw private key +* ``str(PrivateKey)`` + Returns the readable (uncompressed wif format) Graphene private key. This + call is equivalent to ``format(PrivateKey, "WIF")`` +* ``repr(PrivateKey)`` + Gives the hex representation of the Graphene private key. +* ``format(PrivateKey,_format)`` + Formats the instance of :doc:`Base58 ` according to ``_format`` + +Example::: + + PrivateKey("5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd") + +Format vs. Repr +^^^^^^^^^^^^^^^ + +.. code-block:: python + + print("Private Key : " + format(private_key,"WIF")) + print("Secret Exponent (hex) : " + repr(private_key)) + print("BTS PubKey (hex) : " + repr(private_key.pubkey)) + print("BTS PubKey : " + format(private_key.pubkey, "BTS")) + print("BTS Address : " + format(private_key.address,"BTS")) + +Output:: + + Private Key : 5Jdv8JHh4r2tUPtmLq8hp8DkW5vCp9y4UGgj6udjJQjG747FCMc + Secret Exponent (hex) : 6c2662a6ac41bd9132a9f846847761ab4f80c82d519cdf92f40dfcd5e97ec5b5 + BTS PubKey (hex) : 021760b78d93878af16f8c11d22f0784c54782a12a88bbd36be847ab0c8b2994de + BTS PubKey : BTS54nWRnewkASXXTwpn3q4q8noadzXmw4y1KpED3grup7VrDDRmx + BTS Address : BTSCmUwH8G1t3VSZRH5kwxx31tiYDNrzWvyW + +Compressed vs. Uncompressed +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: python + + print("BTC uncomp. Pubkey (hex): " + repr(private_key.uncompressed.pubkey)) + print("BTC Address (uncompr) : " + format(private_key.uncompressed.address,"BTC")) + print("BTC comp. Pubkey (hex) : " + repr(private_key.pubkey)) + print("BTC Address (compr) : " + format(private_key.address,"BTC")) + +Output:: + + BTC uncomp. Pubkey (hex): 041760b78d93878af16f8c11d22f0784c54782a12a88bbd36be847ab0c8b2994de4d5abd46cabab34222023cd9034e1e6c0377fac5579a9c01e46b9498529aaf46 + BTC Address (uncompr) : 1JidAV2npbyLn77jGYQtkpJDjx6Yt5eJSh + BTC comp. Pubkey (hex) : 021760b78d93878af16f8c11d22f0784c54782a12a88bbd36be847ab0c8b2994de + BTC Address (compr) : 1GZ1JCW3kdL4LoCWbzHK4oV6V8JcUGG8HF diff --git a/docs/base58.rst b/docs/base58.rst new file mode 100644 index 00000000..aada0fa2 --- /dev/null +++ b/docs/base58.rst @@ -0,0 +1,45 @@ +Base58 Class +============ + +Requirements +------------ +* python-ecdsa + +Usage +----- + +This module provides following class: + +Base58(object) +^^^^^^^^^^^^^^ + +* ``bytes(Base58)`` + Returns the raw data +* ``str(Base58)`` + Returns the readable ``GrapheneBase58CheckEncoded`` data. +* ``repr(Base58)`` + Gives the hex representation of the data. +* ``format(Base58,_format)`` + Formats the instance according to ``_format``: + + * ``btc``:: + + return base58CheckEncode(0x80, self._hex) + + * ``wif``:: + + return base58CheckEncode(0x00, self._hex) + + * ``bts``:: + + return _format + str(self) + +Examples::: + + format(Base58("02b52e04a0acfe611a4b6963462aca94b6ae02b24e321eda86507661901adb49"),"wif") + repr(Base58("5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd")) + +Output::: + + "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd" + "02b52e04a0acfe611a4b6963462aca94b6ae02b24e321eda86507661901adb49" diff --git a/docs/bip38.rst b/docs/bip38.rst new file mode 100644 index 00000000..aed11294 --- /dev/null +++ b/docs/bip38.rst @@ -0,0 +1,2 @@ +Bip38 Encrypted Private Keys +============================ diff --git a/docs/client.rst b/docs/client.rst new file mode 100644 index 00000000..a2eb0f23 --- /dev/null +++ b/docs/client.rst @@ -0,0 +1,73 @@ +RPC Interface +============= + +Requirements +------------ + +* python-requests + +Configuration +------------- + +The Graphene client needs the RPC interface enabled and username, password, +and port set. The relevant port for this module is the port of the +``httpd_endpoint``. Two ways exist to do so: + +* Command-line parameters: + +.. code-block:: bash + + ./graphene_client --server --httpport 5000 --rpcuser test --rpcpassword test + +* Configuration file settings: + +.. code-block:: json + + "rpc": { + "enable": true, + "rpc_user": "USERNAME", + "rpc_password": "PASSWORD", + "rpc_endpoint": "127.0.0.1:9988", + "httpd_endpoint": "127.0.0.1:19988", <<--- PORT + "htdocs": "./htdocs" + } + +Usage +----- +All RPC commands of the Graphene client are exposed as methods in the class +graphenerpc. Once an instance of graphenerpc is created, i.e.,:: + + import graphenelib + rpc = graphenelib.client("http://localhost:19988/rpc", "username","password") + +any rpc command can be issued using the instance via the syntax +rpc.*command*(*parameters*). Example::: + + print(json.dumps(rpc.getinfo(),indent=4)) + print(json.dumps(rpc.about() ,indent=4)) + + rpc.open_wallet("default") + rpc.ask(account, amount, quote, price, base) + +Unlocking a wallet +------------------ +The ``unlock`` call is overwritten by the library. It takes as a first argument +the timeout (in seconds) until the wallet is relocked automatically by the client and as an +optional second argument the passphrase of the wallet. :: + + rpc.unlock(3600) + +.. note:: + + If no (or an empty) passphrase is given AND the wallet is locked, the + library will ask to manually provide a passphrase. + +Exceptions +---------- + +``UnauthorizedError(Exception)`` + Is thrown if login credentials are invalid. +``RPCError(Exception)`` + Is thrown if the Graphene client returns an error. +``RPCConnection(Exception)`` + Is thrown if no connection can be established diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..c18183d9 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# python-graphenelib documentation build configuration file, created by +# sphinx-quickstart on Fri Jun 5 14:06:38 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import shlex + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'python-graphenelib' +copyright = '2015, Fabian Schuh' +author = 'Fabian Schuh' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'pyramid' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'python-graphenelibdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'python-graphenelib.tex', 'python-graphenelib Documentation', + 'Fabian Schuh', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'python-graphenelib', 'python-graphenelib Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'python-graphenelib', 'python-graphenelib Documentation', + author, 'python-graphenelib', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..71f1a725 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,33 @@ +.. python-graphenelib documentation master file, created by + sphinx-quickstart on Fri Jun 5 14:06:38 2015. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +.. http://sphinx-doc.org/rest.html + http://sphinx-doc.org/markup/index.html + http://sphinx-doc.org/markup/para.html + http://openalea.gforge.inria.fr/doc/openalea/doc/_build/html/source/sphinx/rest_syntax.html + http://rest-sphinx-memo.readthedocs.org/en/latest/ReST.html + +Welcome to python-graphenelib's documentation! +=============================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + address + bip38 + base58 + rpc + paperwallet + transactions + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..f801af9f --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,263 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 2> nul +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\python-graphenelib.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\python-graphenelib.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/docs/paperwallet.rst b/docs/paperwallet.rst new file mode 100644 index 00000000..07d1f40d --- /dev/null +++ b/docs/paperwallet.rst @@ -0,0 +1,2 @@ +Paperwallets +============ diff --git a/docs/transactions.rst b/docs/transactions.rst new file mode 100644 index 00000000..1110d570 --- /dev/null +++ b/docs/transactions.rst @@ -0,0 +1,2 @@ +Transactions +============ diff --git a/graphenelib/__init__.py b/graphenelib/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/graphenelib/address.py b/graphenelib/address.py new file mode 100644 index 00000000..5928e213 --- /dev/null +++ b/graphenelib/address.py @@ -0,0 +1,276 @@ +import ecdsa +import hashlib +from binascii import hexlify,unhexlify +from graphenelib.base58 import ripemd160,Base58 +import unittest +import sys + +""" +This class and the methods require python3 +""" +assert sys.version_info[0] == 3, "graphenelib requires python3" + +class Address(object): + def __init__(self,address=None,pubkey=None): + if pubkey is not None : + self._pubkey = Base58(pubkey) + self._address = None + elif address is not None : + self._pubkey = None + self._address = Base58(address) + else : + raise Exception("Address has to be initialized by either the pubkey or the address.") + + def derivesha256address(self): + pkbin = unhexlify(repr(self._pubkey)) + addressbin = ripemd160(hexlify(hashlib.sha256(pkbin).digest())) + return Base58(hexlify(addressbin).decode('ascii')) + + def derivesha512address(self): + pkbin = unhexlify(repr(self._pubkey)) + addressbin = ripemd160(hexlify(hashlib.sha512(pkbin).digest())) + return Base58(hexlify(addressbin).decode('ascii')) + """ + Returns hex value of object + """ + def __repr__(self) : + return repr(self.derivesha512address()) + """ + Return BTS-base58CheckEncoded string of data + """ + def __str__(self) : + return format(self, "BTS") + """ + Return formated address + """ + def __format__(self,_format) : + if self._address == None : + if _format.lower() == "btc" : + return format(self.derivesha256address(),_format) + else : + return format(self.derivesha512address(),_format) + else : + return format(self._address,_format) + """ + Return raw bytes + """ + def __bytes__(self) : + if self._address == None : + return bytes(self.derivesha512address()) + else : + return bytes(self._address) + +class PublicKey(Address): + def __init__(self,pk,prefix=None): + self._pk = Base58(pk,prefix=prefix) + self.address = Address(pubkey=pk) + self.pubkey = self._pk + """ + Returns hex value of object + """ + def __repr__(self) : + return repr(self._pk) + """ + Return BTS-base58CheckEncoded string of data + """ + def __str__(self) : + return format(self._pk,"BTS") + """ + Return formated pubkey + """ + def __format__(self,_format) : + return format(self._pk, _format) + """ + Return raw bytes + """ + def __bytes__(self) : + return bytes(self._pk) + +class PrivateKey(PublicKey): + def __init__(self,wif=None): + if wif == None : + import os + self._wif = Base58(hexlify(os.urandom(32)).decode('ascii')) + else : + self._wif = Base58(wif) + # compress pubkeys only + self._pubkeyhex, self._pubkeyuncompressedhex = self.compressedpubkey() + self.pubkey = PublicKey(self._pubkeyhex) + self.uncompressed = PublicKey(self._pubkeyuncompressedhex) + self.uncompressed.address = Address(pubkey=self._pubkeyuncompressedhex) + self.address = Address(pubkey=self._pubkeyhex) + + def compressedpubkey(self): + secret = unhexlify(repr(self._wif)) + order = ecdsa.SigningKey.from_string(secret, curve=ecdsa.SECP256k1).curve.generator.order() + p = ecdsa.SigningKey.from_string(secret, curve=ecdsa.SECP256k1).verifying_key.pubkey.point + x_str = ecdsa.util.number_to_string(p.x(), order) + y_str = ecdsa.util.number_to_string(p.y(), order) + compressed = hexlify(bytes(chr(2 + (p.y() & 1)),'ascii') + x_str ).decode('ascii') + uncompressed = hexlify(bytes(chr(4) ,'ascii') + x_str + y_str).decode('ascii') + return([compressed, uncompressed]) + + """ + Return formated pubkey + """ + def __format__(self,_format) : + return format(self._wif,_format) + """ + Returns hex value of object + """ + def __repr__(self) : + return repr(self._wif) + """ + Return BTS-base58CheckEncoded string of data + """ + def __str__(self) : + return format(self._wif,"WIF") + """ + Return raw bytes + """ + def __bytes__(self) : + return bytes(self._wif) + +class Testcases(unittest.TestCase) : + def test_B85hexgetb58_btc(self): + self.assertEqual([ "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd", + "5JWcdkhL3w4RkVPcZMdJsjos22yB5cSkPExerktvKnRNZR5gx1S", + "5HvVz6XMx84aC5KaaBbwYrRLvWE46cH6zVnv4827SBPLorg76oq", + "5Jete5oFNjjk3aUMkKuxgAXsp7ZyhgJbYNiNjHLvq5xzXkiqw7R", + "5KDT58ksNsVKjYShG4Ls5ZtredybSxzmKec8juj7CojZj6LPRF7", + "02b52e04a0acfe611a4b6963462aca94b6ae02b24e321eda86507661901adb49", + "5b921f7051be5e13e177a0253229903c40493df410ae04f4a450c85568f19131", + "0e1bfc9024d1f55a7855dc690e45b2e089d2d825a4671a3c3c7e4ea4e74ec00e", + "6e5cc4653d46e690c709ed9e0570a2c75a286ad7c1bc69a648aae6855d919d3e", + ],[ + format(Base58("02b52e04a0acfe611a4b6963462aca94b6ae02b24e321eda86507661901adb49"),"WIF"), + format(Base58("5b921f7051be5e13e177a0253229903c40493df410ae04f4a450c85568f19131"),"WIF"), + format(Base58("0e1bfc9024d1f55a7855dc690e45b2e089d2d825a4671a3c3c7e4ea4e74ec00e"),"WIF"), + format(Base58("6e5cc4653d46e690c709ed9e0570a2c75a286ad7c1bc69a648aae6855d919d3e"),"WIF"), + format(Base58("b84abd64d66ee1dd614230ebbe9d9c6d66d78d93927c395196666762e9ad69d8"),"WIF"), + repr(Base58("5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd")), + repr(Base58("5JWcdkhL3w4RkVPcZMdJsjos22yB5cSkPExerktvKnRNZR5gx1S")), + repr(Base58("5HvVz6XMx84aC5KaaBbwYrRLvWE46cH6zVnv4827SBPLorg76oq")), + repr(Base58("5Jete5oFNjjk3aUMkKuxgAXsp7ZyhgJbYNiNjHLvq5xzXkiqw7R")), + ]) + def test_B85hexgetb58(self): + self.assertEqual([ 'BTS2CAbTi1ZcgMJ5otBFZSGZJKJenwGa9NvkLxsrS49Kr8JsiSGc', + 'BTShL45FEyUVSVV1LXABQnh4joS9FsUaffRtsdarB5uZjPsrwMZF', + 'BTS7DQR5GsfVaw4wJXzA3TogDhuQ8tUR2Ggj8pwyNCJXheHehL4Q', + 'BTSqc4QMAJHAkna65i8U4b7nkbWk4VYSWpZebW7JBbD7MN8FB5sc', + 'BTS2QAVTJnJQvLUY4RDrtxzX9jS39gEq8gbqYMWjgMxvsvZTJxDSu' + ],[ + format(Base58("02b52e04a0acfe611a4b6963462aca94b6ae02b24e321eda86507661901adb49"),"BTS"), + format(Base58("5b921f7051be5e13e177a0253229903c40493df410ae04f4a450c85568f19131"),"BTS"), + format(Base58("0e1bfc9024d1f55a7855dc690e45b2e089d2d825a4671a3c3c7e4ea4e74ec00e"),"BTS"), + format(Base58("6e5cc4653d46e690c709ed9e0570a2c75a286ad7c1bc69a648aae6855d919d3e"),"BTS"), + format(Base58("b84abd64d66ee1dd614230ebbe9d9c6d66d78d93927c395196666762e9ad69d8"),"BTS")]) + def test_Adress(self): + self.assertEqual([ + format(Address("BTSFN9r6VYzBK8EKtMewfNbfiGCr56pHDBFi"),"BTS"), + format(Address("BTSdXrrTXimLb6TEt3nHnePwFmBT6Cck112" ),"BTS"), + format(Address("BTSJQUAt4gz4civ8gSs5srTK4r82F7HvpChk"),"BTS"), + format(Address("BTSFPXXHXXGbyTBwdKoJaAPXRnhFNtTRS4EL"),"BTS"), + format(Address("BTS3qXyZnjJneeAddgNDYNYXbF7ARZrRv5dr"),"BTS"), + ],[ + "BTSFN9r6VYzBK8EKtMewfNbfiGCr56pHDBFi", + "BTSdXrrTXimLb6TEt3nHnePwFmBT6Cck112", + "BTSJQUAt4gz4civ8gSs5srTK4r82F7HvpChk", + "BTSFPXXHXXGbyTBwdKoJaAPXRnhFNtTRS4EL", + "BTS3qXyZnjJneeAddgNDYNYXbF7ARZrRv5dr", + ]) + def test_PubKey(self): + self.assertEqual([ + format(PublicKey("BTS6UtYWWs3rkZGV8JA86qrgkG6tyFksgECefKE1MiH4HkLD8PFGL", prefix="BTS").address,"BTS"), + format(PublicKey("BTS8YAMLtNcnqGNd3fx28NP3WoyuqNtzxXpwXTkZjbfe9scBmSyGT", prefix="BTS").address,"BTS"), + format(PublicKey("BTS7HUo6bm7Gfoi3RqAtzwZ83BFCwiCZ4tp37oZjtWxGEBJVzVVGw", prefix="BTS").address,"BTS"), + format(PublicKey("BTS6676cZ9qmqPnWMrm4McjCuHcnt6QW5d8oRJ4t8EDH8DdCjvh4V", prefix="BTS").address,"BTS"), + format(PublicKey("BTS7u8m6zUNuzPNK1tPPLtnipxgqV9mVmTzrFNJ9GvovvSTCkVUra", prefix="BTS").address,"BTS") + ],[ + "BTS66FCjYKzMwLbE3a59YpmFqA9bwporT4L3", + "BTSKNpRuPX8KhTBsJoFp1JXd7eQEsnCpRw3k", + "BTS838ENJargbUrxXWuE2xD9HKjQaS17GdCd", + "BTSNsrLFWTziSZASnNJjWafFtGBfSu8VG8KU", + "BTSDjAGuXzk3WXabBEgKKc8NsuQM412boBdR" + ]) + def test_btsprivkey(self): + self.assertEqual([ + format(PrivateKey("5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd").address,"BTS"), + format(PrivateKey("5JWcdkhL3w4RkVPcZMdJsjos22yB5cSkPExerktvKnRNZR5gx1S").address,"BTS"), + format(PrivateKey("5HvVz6XMx84aC5KaaBbwYrRLvWE46cH6zVnv4827SBPLorg76oq").address,"BTS"), + format(PrivateKey("5Jete5oFNjjk3aUMkKuxgAXsp7ZyhgJbYNiNjHLvq5xzXkiqw7R").address,"BTS"), + format(PrivateKey("5KDT58ksNsVKjYShG4Ls5ZtredybSxzmKec8juj7CojZj6LPRF7").address,"BTS") + ],[ + "BTSFN9r6VYzBK8EKtMewfNbfiGCr56pHDBFi", + "BTSdXrrTXimLb6TEt3nHnePwFmBT6Cck112", + "BTSJQUAt4gz4civ8gSs5srTK4r82F7HvpChk", + "BTSFPXXHXXGbyTBwdKoJaAPXRnhFNtTRS4EL", + "BTS3qXyZnjJneeAddgNDYNYXbF7ARZrRv5dr", + ]) + def test_btcprivkey(self): + self.assertEqual([ + format(PrivateKey("5HvVz6XMx84aC5KaaBbwYrRLvWE46cH6zVnv4827SBPLorg76oq").uncompressed.address,"BTC"), + format(PrivateKey("5Jete5oFNjjk3aUMkKuxgAXsp7ZyhgJbYNiNjHLvq5xzXkiqw7R").uncompressed.address,"BTC"), + format(PrivateKey("5KDT58ksNsVKjYShG4Ls5ZtredybSxzmKec8juj7CojZj6LPRF7").uncompressed.address,"BTC"), + ],[ + "1G7qw8FiVfHEFrSt3tDi6YgfAdrDrEM44Z", + "12c7KAAZfpREaQZuvjC5EhpoN6si9vekqK", + "1Gu5191CVHmaoU3Zz3prept87jjnpFDrXL", + ]) + + def test_PublicKey(self): + self.assertEqual([ + str(PublicKey("BTS6UtYWWs3rkZGV8JA86qrgkG6tyFksgECefKE1MiH4HkLD8PFGL",prefix="BTS")), + str(PublicKey("BTS8YAMLtNcnqGNd3fx28NP3WoyuqNtzxXpwXTkZjbfe9scBmSyGT",prefix="BTS")), + str(PublicKey("BTS7HUo6bm7Gfoi3RqAtzwZ83BFCwiCZ4tp37oZjtWxGEBJVzVVGw",prefix="BTS")), + str(PublicKey("BTS6676cZ9qmqPnWMrm4McjCuHcnt6QW5d8oRJ4t8EDH8DdCjvh4V",prefix="BTS")), + str(PublicKey("BTS7u8m6zUNuzPNK1tPPLtnipxgqV9mVmTzrFNJ9GvovvSTCkVUra",prefix="BTS")) + ],[ + "BTS6UtYWWs3rkZGV8JA86qrgkG6tyFksgECefKE1MiH4HkLD8PFGL", + "BTS8YAMLtNcnqGNd3fx28NP3WoyuqNtzxXpwXTkZjbfe9scBmSyGT", + "BTS7HUo6bm7Gfoi3RqAtzwZ83BFCwiCZ4tp37oZjtWxGEBJVzVVGw", + "BTS6676cZ9qmqPnWMrm4McjCuHcnt6QW5d8oRJ4t8EDH8DdCjvh4V", + "BTS7u8m6zUNuzPNK1tPPLtnipxgqV9mVmTzrFNJ9GvovvSTCkVUra" + ]) + + def test_Privatekey(self): + self.assertEqual([ + str(PrivateKey("5HvVz6XMx84aC5KaaBbwYrRLvWE46cH6zVnv4827SBPLorg76oq")), + str(PrivateKey("5Jete5oFNjjk3aUMkKuxgAXsp7ZyhgJbYNiNjHLvq5xzXkiqw7R")), + str(PrivateKey("5KDT58ksNsVKjYShG4Ls5ZtredybSxzmKec8juj7CojZj6LPRF7")), + repr(PrivateKey("5HvVz6XMx84aC5KaaBbwYrRLvWE46cH6zVnv4827SBPLorg76oq")), + repr(PrivateKey("5Jete5oFNjjk3aUMkKuxgAXsp7ZyhgJbYNiNjHLvq5xzXkiqw7R")), + repr(PrivateKey("5KDT58ksNsVKjYShG4Ls5ZtredybSxzmKec8juj7CojZj6LPRF7")), + ],[ + "5HvVz6XMx84aC5KaaBbwYrRLvWE46cH6zVnv4827SBPLorg76oq", + "5Jete5oFNjjk3aUMkKuxgAXsp7ZyhgJbYNiNjHLvq5xzXkiqw7R", + "5KDT58ksNsVKjYShG4Ls5ZtredybSxzmKec8juj7CojZj6LPRF7", + '0e1bfc9024d1f55a7855dc690e45b2e089d2d825a4671a3c3c7e4ea4e74ec00e', + '6e5cc4653d46e690c709ed9e0570a2c75a286ad7c1bc69a648aae6855d919d3e', + 'b84abd64d66ee1dd614230ebbe9d9c6d66d78d93927c395196666762e9ad69d8' + ]) + + +def keyinfo(private_key) : + print("-"*80) + print("Private Key : " + format(private_key,"WIF")) + print("Secret Exponent (hex) : " + repr(private_key)) + print("-"*80) + print("BTC uncomp. Pubkey (hex): " + repr(private_key.uncompressed.pubkey)) + print("BTC Address (uncompr) : " + format(private_key.uncompressed.address,"BTC")) + print("-"*80) + print("BTC comp. Pubkey (hex) : " + repr(private_key.pubkey)) + print("BTC Address (compr) : " + format(private_key.address,"BTC")) + print("-"*80) + print("BTS PubKey (hex) : " + repr(private_key.pubkey)) + print("BTS PubKey : " + format(private_key.pubkey,"BTS")) + print("BTS Address : " + format(private_key.address,"BTS")) + print("-"*80) + +if __name__ == '__main__': + unittest.main() + + # Generate a random private key or take it from input as WIF + #import sys + #if len( sys.argv ) < 2 : keyinfo(PrivateKey()) + #else : keyinfo(PrivateKey(sys.argv[1])) diff --git a/graphenelib/base58.py b/graphenelib/base58.py new file mode 100644 index 00000000..fa94f809 --- /dev/null +++ b/graphenelib/base58.py @@ -0,0 +1,227 @@ +from binascii import hexlify, unhexlify +import hashlib +import sys +import string +import unittest + +""" +This class and the methods require python3 +""" +assert sys.version_info[0] == 3, "graphenelib requires python3" + +""" +Default Prefix +""" +PREFIX = "BTS" + +""" +Base58 Class +""" +class Base58(object) : + def __init__(self,data,prefix=PREFIX) : + self._prefix = prefix + if all(c in string.hexdigits for c in data) : + self._hex = data + elif data[0] == "5" or data[0] == "6" : + self._hex = base58CheckDecode(data) + elif data[:len(PREFIX)] == self._prefix : + self._hex = btsBase58CheckDecode(data[len(PREFIX):]) + else : + raise Exception("Error loading Base58 object") + """ + Format output according to argument _format (wif,btc,bts) + """ + def __format__(self, _format) : + if _format.lower() == "wif" : + return base58CheckEncode(0x80, self._hex) + elif _format.lower() == "btc": + return base58CheckEncode(0x00, self._hex) + elif _format.lower() == "bts" : + return _format + str(self) + else : + raise Exception("Format %s unkown." %_format) + """ + Returns hex value of object + """ + def __repr__(self) : + return self._hex + """ + Return BTS-base58CheckEncoded string of data + """ + def __str__(self) : + return btsBase58CheckEncode(self._hex) + """ + Return raw bytes + """ + def __bytes__(self) : + return unhexlify(self._hex) + +# https://github.com/tochev/python3-cryptocoins/raw/master/cryptocoins/base58.py +BASE58_ALPHABET = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" +def base58decode(base58_str): + base58_text = bytes(base58_str, "ascii") + n = 0 + leading_zeroes_count = 0 + for b in base58_text: + n = n * 58 + BASE58_ALPHABET.find(b) + if n == 0: + leading_zeroes_count += 1 + res = bytearray() + while n >= 256: + div, mod = divmod(n, 256) + res.insert(0, mod) + n = div + else: + res.insert(0, n) + return hexlify(bytearray(1)*leading_zeroes_count + res).decode('ascii') + +def base58encode(hexstring): + byteseq = bytes(unhexlify(bytes(hexstring,'ascii'))) + n = 0 + leading_zeroes_count = 0 + for c in byteseq: + n = n * 256 + c + if n == 0: + leading_zeroes_count += 1 + res = bytearray() + while n >= 58: + div, mod = divmod(n, 58) + res.insert(0, BASE58_ALPHABET[mod]) + n = div + else: + res.insert(0, BASE58_ALPHABET[n]) + return (BASE58_ALPHABET[0:1] * leading_zeroes_count + res).decode('ascii') + +def ripemd160(s): + ripemd160 = hashlib.new('ripemd160') + ripemd160.update(unhexlify(s)) + return ripemd160.digest() + +def doublesha256(s): + return hashlib.sha256(hashlib.sha256(unhexlify(s)).digest()).digest() + +def b58encode(v) : + return base58encode(v) + +def b58decode(v) : + return base58decode(v) + +def base58CheckEncode(version, payload): + s = ('%.2x'%version) + payload + checksum = doublesha256(s)[:4] + result = s + hexlify(checksum).decode('ascii') + return base58encode(result) + +def base58CheckDecode(s): + s = unhexlify( base58decode(s) ) + dec = hexlify(s[:-4]).decode('ascii') + checksum = doublesha256(dec)[:4] + assert(s[-4:] == checksum) + return dec[2:] + +def btsBase58CheckEncode(s): + checksum = ripemd160(s)[:4] + result = s + hexlify(checksum).decode('ascii') + return base58encode(result) + +def btsBase58CheckDecode(s): + s = unhexlify( base58decode(s) ) + dec = hexlify(s[:-4]).decode('ascii') + checksum = ripemd160(dec)[:4] + assert(s[-4:] == checksum) + return dec + +class Testcases(unittest.TestCase) : + def test_base58decode(self): + self.assertEqual([ base58decode('5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ'), + base58decode('5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss'), + base58decode('5KfazyjBBtR2YeHjNqX5D6MXvqTUd2iZmWusrdDSUqoykTyWQZB')], + [ '800c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d507a5b8d', + '80e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8555c5bbb26', + '80f3a375e00cc5147f30bee97bb5d54b31a12eee148a1ac31ac9edc4ecd13bc1f80cc8148e']) + def test_base58encode(self): + self.assertEqual([ '5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ', + '5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss', + '5KfazyjBBtR2YeHjNqX5D6MXvqTUd2iZmWusrdDSUqoykTyWQZB'], + [ base58encode('800c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d507a5b8d'), + base58encode('80e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8555c5bbb26'), + base58encode('80f3a375e00cc5147f30bee97bb5d54b31a12eee148a1ac31ac9edc4ecd13bc1f80cc8148e')]) + def test_btsBase58CheckEncode(self): + self.assertEqual( [ btsBase58CheckEncode("02e649f63f8e8121345fd7f47d0d185a3ccaa843115cd2e9392dcd9b82263bc680"), + btsBase58CheckEncode("021c7359cd885c0e319924d97e3980206ad64387aff54908241125b3a88b55ca16"), + btsBase58CheckEncode("02f561e0b57a552df3fa1df2d87a906b7a9fc33a83d5d15fa68a644ecb0806b49a"), + btsBase58CheckEncode("03e7595c3e6b58f907bee951dc29796f3757307e700ecf3d09307a0cc4a564eba3"),], + [ "6dumtt9swxCqwdPZBGXh9YmHoEjFFnNfwHaTqRbQTghGAY2gRz", + "5725vivYpuFWbeyTifZ5KevnHyqXCi5hwHbNU9cYz1FHbFXCxX", + "6kZKHSuxqAwdCYsMvwTcipoTsNE2jmEUNBQufGYywpniBKXWZK", + "8b82mpnH8YX1E9RHnU2a2YgLTZ8ooevEGP9N15c1yFqhoBvJur" ]) + def test_btsBase58CheckDecode(self): + self.assertEqual( [ "02e649f63f8e8121345fd7f47d0d185a3ccaa843115cd2e9392dcd9b82263bc680", + "021c7359cd885c0e319924d97e3980206ad64387aff54908241125b3a88b55ca16", + "02f561e0b57a552df3fa1df2d87a906b7a9fc33a83d5d15fa68a644ecb0806b49a", + "03e7595c3e6b58f907bee951dc29796f3757307e700ecf3d09307a0cc4a564eba3",], + [ btsBase58CheckDecode("6dumtt9swxCqwdPZBGXh9YmHoEjFFnNfwHaTqRbQTghGAY2gRz"), + btsBase58CheckDecode("5725vivYpuFWbeyTifZ5KevnHyqXCi5hwHbNU9cYz1FHbFXCxX"), + btsBase58CheckDecode("6kZKHSuxqAwdCYsMvwTcipoTsNE2jmEUNBQufGYywpniBKXWZK"), + btsBase58CheckDecode("8b82mpnH8YX1E9RHnU2a2YgLTZ8ooevEGP9N15c1yFqhoBvJur") ]) + def test_btsb58(self): + self.assertEqual( [ "02e649f63f8e8121345fd7f47d0d185a3ccaa843115cd2e9392dcd9b82263bc680", + "03457298c4b2c56a8d572c051ca3109dabfe360beb144738180d6c964068ea3e58", + "021c7359cd885c0e319924d97e3980206ad64387aff54908241125b3a88b55ca16", + "02f561e0b57a552df3fa1df2d87a906b7a9fc33a83d5d15fa68a644ecb0806b49a", + "03e7595c3e6b58f907bee951dc29796f3757307e700ecf3d09307a0cc4a564eba3"], + [ btsBase58CheckDecode(btsBase58CheckEncode("02e649f63f8e8121345fd7f47d0d185a3ccaa843115cd2e9392dcd9b82263bc680")), + btsBase58CheckDecode(btsBase58CheckEncode("03457298c4b2c56a8d572c051ca3109dabfe360beb144738180d6c964068ea3e58")), + btsBase58CheckDecode(btsBase58CheckEncode("021c7359cd885c0e319924d97e3980206ad64387aff54908241125b3a88b55ca16")), + btsBase58CheckDecode(btsBase58CheckEncode("02f561e0b57a552df3fa1df2d87a906b7a9fc33a83d5d15fa68a644ecb0806b49a")), + btsBase58CheckDecode(btsBase58CheckEncode("03e7595c3e6b58f907bee951dc29796f3757307e700ecf3d09307a0cc4a564eba3"))]) + def test_Base58CheckDecode(self): + self.assertEqual( [ "02e649f63f8e8121345fd7f47d0d185a3ccaa843115cd2e9392dcd9b82263bc680", + "021c7359cd885c0e319924d97e3980206ad64387aff54908241125b3a88b55ca16", + "02f561e0b57a552df3fa1df2d87a906b7a9fc33a83d5d15fa68a644ecb0806b49a", + "03e7595c3e6b58f907bee951dc29796f3757307e700ecf3d09307a0cc4a564eba3", + "02b52e04a0acfe611a4b6963462aca94b6ae02b24e321eda86507661901adb49", + "5b921f7051be5e13e177a0253229903c40493df410ae04f4a450c85568f19131", + "0e1bfc9024d1f55a7855dc690e45b2e089d2d825a4671a3c3c7e4ea4e74ec00e", + "6e5cc4653d46e690c709ed9e0570a2c75a286ad7c1bc69a648aae6855d919d3e", + "b84abd64d66ee1dd614230ebbe9d9c6d66d78d93927c395196666762e9ad69d8", + ], [ + base58CheckDecode("KwKM6S22ZZDYw5dxBFhaRyFtcuWjaoxqDDfyCcBYSevnjdfm9Cjo"), + base58CheckDecode("KwHpCk3sLE6VykHymAEyTMRznQ1Uh5ukvFfyDWpGToT7Hf5jzrie"), + base58CheckDecode("KwKTjyQbKe6mfrtsf4TFMtqAf5as5bSp526s341PQEQvq5ZzEo5W"), + base58CheckDecode("KwMJJgtyBxQ9FEvUCzJmvr8tXxB3zNWhkn14mWMCTGSMt5GwGLgz"), + base58CheckDecode("5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd"), + base58CheckDecode("5JWcdkhL3w4RkVPcZMdJsjos22yB5cSkPExerktvKnRNZR5gx1S"), + base58CheckDecode("5HvVz6XMx84aC5KaaBbwYrRLvWE46cH6zVnv4827SBPLorg76oq"), + base58CheckDecode("5Jete5oFNjjk3aUMkKuxgAXsp7ZyhgJbYNiNjHLvq5xzXkiqw7R"), + base58CheckDecode("5KDT58ksNsVKjYShG4Ls5ZtredybSxzmKec8juj7CojZj6LPRF7"), + ]) + def test_base58CheckEncodeDecopde(self): + self.assertEqual( [ "02e649f63f8e8121345fd7f47d0d185a3ccaa843115cd2e9392dcd9b82263bc680", + "03457298c4b2c56a8d572c051ca3109dabfe360beb144738180d6c964068ea3e58", + "021c7359cd885c0e319924d97e3980206ad64387aff54908241125b3a88b55ca16", + "02f561e0b57a552df3fa1df2d87a906b7a9fc33a83d5d15fa68a644ecb0806b49a", + "03e7595c3e6b58f907bee951dc29796f3757307e700ecf3d09307a0cc4a564eba3"], + [ base58CheckDecode(base58CheckEncode(0x80,"02e649f63f8e8121345fd7f47d0d185a3ccaa843115cd2e9392dcd9b82263bc680")), + base58CheckDecode(base58CheckEncode(0x80,"03457298c4b2c56a8d572c051ca3109dabfe360beb144738180d6c964068ea3e58")), + base58CheckDecode(base58CheckEncode(0x80,"021c7359cd885c0e319924d97e3980206ad64387aff54908241125b3a88b55ca16")), + base58CheckDecode(base58CheckEncode(0x80,"02f561e0b57a552df3fa1df2d87a906b7a9fc33a83d5d15fa68a644ecb0806b49a")), + base58CheckDecode(base58CheckEncode(0x80,"03e7595c3e6b58f907bee951dc29796f3757307e700ecf3d09307a0cc4a564eba3"))]) + + def test_Base58(self) : + self.assertEqual( [ + format(Base58("02b52e04a0acfe611a4b6963462aca94b6ae02b24e321eda86507661901adb49"),"wif"), + format(Base58("5b921f7051be5e13e177a0253229903c40493df410ae04f4a450c85568f19131"),"wif"), + format(Base58("0e1bfc9024d1f55a7855dc690e45b2e089d2d825a4671a3c3c7e4ea4e74ec00e"),"wif"), + format(Base58("6e5cc4653d46e690c709ed9e0570a2c75a286ad7c1bc69a648aae6855d919d3e"),"wif"), + format(Base58("b84abd64d66ee1dd614230ebbe9d9c6d66d78d93927c395196666762e9ad69d8"),"wif"), + ], [ + "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd", + "5JWcdkhL3w4RkVPcZMdJsjos22yB5cSkPExerktvKnRNZR5gx1S", + "5HvVz6XMx84aC5KaaBbwYrRLvWE46cH6zVnv4827SBPLorg76oq", + "5Jete5oFNjjk3aUMkKuxgAXsp7ZyhgJbYNiNjHLvq5xzXkiqw7R", + "5KDT58ksNsVKjYShG4Ls5ZtredybSxzmKec8juj7CojZj6LPRF7", + ]) + +if __name__ == "__main__": + unittest.main() diff --git a/graphenelib/bip38.py b/graphenelib/bip38.py new file mode 100644 index 00000000..44ce9c68 --- /dev/null +++ b/graphenelib/bip38.py @@ -0,0 +1,89 @@ +#!/usr/bin/python + +from Crypto.Cipher import AES +import scrypt +import hashlib +from binascii import hexlify, unhexlify +from graphenelib.address import PrivateKey, PublicKey, Address +from graphenelib.base58 import ripemd160,Base58,base58encode,base58decode +import unittest +import sys + +""" +This class and the methods require python3 +""" +assert sys.version_info[0] == 3, "graphenelib requires python3" + +def _encrypt_xor(a, b, aes): + 'Returns encrypt(a ^ b).' + return aes.encrypt(unhexlify('%0.32x' % (int((a), 16) ^ int(hexlify(b), 16)))) + +def encrypt(privkey,passphrase): + '''BIP0038 non-ec-multiply encryption. Returns BIP0038 encrypted privkey.''' + privkeyraw = bytes(privkey) # bytes + privkeyhex = repr(privkey) # hex + addr = format(privkey.uncompressed.address,"BTC") + salt = hashlib.sha256(hashlib.sha256(bytes(addr,'ascii')).digest()).digest()[0:4] + key = scrypt.hash(passphrase, salt, 16384, 8, 8) + (derived_half1, derived_half2) = (key[:32], key[32:]) + aes = AES.new(derived_half2) + encrypted_half1 = _encrypt_xor(privkeyhex[:32], derived_half1[:16], aes) + encrypted_half2 = _encrypt_xor(privkeyhex[32:], derived_half1[16:], aes) + # flag byte is forced 0xc0 because BTS only uses compressed keys + payload = b'\x01' + b'\x42' + b'\xc0' + salt + encrypted_half1 + encrypted_half2 + checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4] # b58check for encrypted privkey + privatkey = hexlify(payload + checksum).decode('ascii') + return Base58(privatkey) + +def decrypt(encrypted_privkey,passphrase): + '''BIP0038 non-ec-multiply decryption. Returns WIF privkey.''' + d = unhexlify(base58decode(encrypted_privkey)) + d = d[2:] # remove trailing 0x01 and 0x42 + flagbyte = d[0:1] # get flag byte + d = d[1:] # get payload + assert flagbyte == b'\xc0', "Flagbyte has to be 0xc0" + salt = d[0:4] + d = d[4:-4] + key = scrypt.hash(passphrase,salt, 16384, 8, 8) + derivedhalf1 = key[0:32] + derivedhalf2 = key[32:64] + encryptedhalf1 = d[0:16] + encryptedhalf2 = d[16:32] + aes = AES.new(derivedhalf2) + decryptedhalf2 = aes.decrypt(encryptedhalf2) + decryptedhalf1 = aes.decrypt(encryptedhalf1) + privraw = decryptedhalf1 + decryptedhalf2 + privraw = ('%064x' % (int(hexlify(privraw), 16) ^ int(hexlify(derivedhalf1), 16))) + wif = Base58(privraw) + privkey = PrivateKey(format(wif,"wif")) + addr = format(privkey.uncompressed.address,"BTC") + saltverify = hashlib.sha256(hashlib.sha256(bytes(addr,'ascii')).digest()).digest()[0:4] + if saltverify != salt : + raise Exception('salt verification failed! Password is likely incorrect.') + return wif + +class Testcases(unittest.TestCase) : + def test_encrypt(self): + self.assertEqual([ + format(encrypt(PrivateKey("5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd"),"TestingOneTwoThree"), "encwif"), + format(encrypt(PrivateKey("5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR"),"TestingOneTwoThree"), "encwif"), + format(encrypt(PrivateKey("5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5"),"Satoshi"), "encwif"), + ],[ + "6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxThxsUW8epQi", + "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg", + "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq", + ]) + + def test_deencrypt(self): + self.assertEqual([ + format(decrypt("6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxThxsUW8epQi","TestingOneTwoThree"),"wif"), + format(decrypt("6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg","TestingOneTwoThree"),"wif"), + format(decrypt("6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq","Satoshi"),"wif") + ],[ + "5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd", + "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR", + "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5", + ]) + +if __name__ == '__main__': + unittest.main() diff --git a/graphenelib/client.py b/graphenelib/client.py new file mode 100644 index 00000000..036bce91 --- /dev/null +++ b/graphenelib/client.py @@ -0,0 +1,240 @@ +import time +import sys +import json +import asyncio +from functools import partial +try : + from autobahn.asyncio.websocket import WebSocketClientProtocol, \ + WebSocketClientFactory +except ImportError: + raise ImportError( "Missing dependency: python-autobahn" ) +try : + import requests +except ImportError: + raise ImportError( "Missing dependency: python-requests" ) + +""" +Error Classes +""" +class UnauthorizedError(Exception) : + pass + +class RPCError(Exception) : + pass + +class RPCConnection(Exception) : + pass + +class GrapheneWebsocketProtocol(WebSocketClientProtocol): + database_callbacks = [] + + def __init__(self) : + self.request_id = None + self.api_ids = {} + self.requests = {} + + def setObjectCallbacks(self, callbacks) : + self.database_callbacks = callbacks + + def onConnect(self, response) : + self.request_id = 1 + print("Server connected: {0}".format(response.peer)) + + def rpcexec(self, params, callback=None) : + request = {"request":{},"callback":None} + self.request_id += 1 + request["id"] = self.request_id + request["request"]["id"] = self.request_id + request["request"]["method"] = "call" + request["request"]["params"] = params + request["callback"] = callback + self.requests.update({self.request_id : request}) + self.sendMessage(json.dumps(request["request"]).encode('utf8')) + + def _set_api_id(self, name, data) : + self.api_ids.update({ name : data["result"] }) + + def _login(self) : + self.rpcexec([1,"login",[self.username,self.password]]) + + def subscribe_to_objects(self, objs, data) : + self.rpcexec([self.api_ids["database"],"subscribe_to_objects", objs]) + + def onOpen(self): + print("WebSocket connection open.") + self._login() + + """ Register with network_broadcast + """ + self.rpcexec([1,"network_broadcast",[]], [\ + partial(self._set_api_id, "network_broadcast"), + ]) + + """ Register with database and subscribe to objects + """ + handles = [partial(self._set_api_id, "database")] + for i,handle in enumerate(self.database_callbacks) : + handles.append(partial(self.subscribe_to_objects, [i,[handle]])) + self.rpcexec([1,"database",[]], handles) + + def onMessage(self, payload, isBinary): + res = json.loads(payload.decode('utf8')) + if "error" not in res : + """ Resolve answers from RPC calls + """ + if "id" in res : + if res["id"] not in self.requests : + print("Received answer to an unknown request?!") + else : + callbacks = self.requests[res["id"]]["callback"] + if callbacks == None : + pass + elif isinstance(callbacks,list) : + for callback in callbacks : + callback(res) + else : + callbacks(res) + elif "method" in res : + """ Run registered call backs for individual object notices + """ + if res["method"] == "notice" : + callbackID = res["params"][0] + callbackhandler = list(self.database_callbacks.values())[callbackID] + callbackhandler(res["params"][1]) + else : + print("Error! ", res) + + def connection_lost(self) : + pass + + def onClose(self, wasClean, code, reason): + print("WebSocket connection closed: {0}".format(reason)) + +class GrapheneClient(object): + """ Constructor takes host, port, and login credentials + """ + def __init__(self, host, port, username, password) : + self.username = username + self.password = password + self.host = host + self.port = port + self.headers = {'content-type': 'application/json'} + + """ Get an object_id by name + """ + def object_id(self, name, instance=0) : + objects = { + "NULL" : "1.0.%d", + "BASE" : "1.1.%d", + "ACCOUNT" : "1.2.%d", + "FORCE_SETTLEMENT" : "1.3.%d", + "ASSET" : "1.4.%d", + "DELEGATE" : "1.5.%d", + "WITNESS" : "1.6.%d", + "LIMIT_ORDER" : "1.7.%d", + "CALL_ORDER" : "1.8.%d", + "CUSTOM" : "1.9.%d", + "PROPOSAL" : "1.10.%d", + "OPERATION_HISTORY" : "1.11.%d", + "WITHDRAW_PERMISSION" : "1.12.%d", + "VESTING_BALANCE" : "1.13.%d", + "WORKER" : "1.14.%d", + "BALANCE" : "1.15.%d", + "GLOBAL_PROPERTY" : "2.0.%d", + "DYNAMIC_GLOBAL_PROPERTY" : "2.1.%d", + "INDEX_META" : "2.2.%d", + "ASSET_DYNAMIC_DATA" : "2.3.%d", + "ASSET_BITASSET_DATA" : "2.4.%d", + "DELEGATE_FEEDS" : "2.5.%d", + "ACCOUNT_BALANCE" : "2.6.%d", + "ACCOUNT_STATISTICS" : "2.7.%d", + "ACCOUNT_DEBT" : "2.8.%d", + "TRANSACTION" : "2.9.%d", + "BLOCK_SUMMARY" : "2.10.%d", + "ACCOUNT_TRANSACTION_HISTORY" : "2.11.%d", + "WITNESS_SCHEDULE" : "2.12.%d", + } + return objects[name]%instance + + """ Define Callbacks on Objects for websocket connections + """ + def setObjectCallbacks(self, callbacks) : + self.proto = GrapheneWebsocketProtocol + self.proto.username = self.username + self.proto.password = self.password + self.proto.database_callbacks = callbacks + + """ Create websocket factory by Autobahn + """ + def connect(self) : + self.factory = WebSocketClientFactory("ws://{}:{:d}".format(self.host, self.port), debug=False) + self.factory.protocol = self.proto + + """ Run websocket forever and wait for events + """ + def run_forever(self) : + loop = asyncio.get_event_loop() + coro = loop.create_connection(self.factory, self.host, self.port) + loop.run_until_complete(coro) + try: + loop.run_forever() + except KeyboardInterrupt: + pass + finally: + loop.close() + + """ Manual execute a command on API + """ + def rpcexec(self,payload) : + try : + response = requests.post("http://{}:{}/rpc".format(self.host,self.port), + data=json.dumps(payload), + headers=self.headers, + auth=(self.username,self.password)) + if response.status_code == 401 : + raise UnauthorizedError + ret = json.loads(response.text) + if 'error' in ret : + if 'detail' in ret['error']: + raise RPCError(ret['error']['detail']) + else: + raise RPCError(ret['error']['message']) + except requests.exceptions.RequestException : + raise RPCConnection("Error connecting to Client. Check hostname and port!") + except UnauthorizedError : + raise UnauthorizedError("Invalid login credentials!") + except ValueError : + raise ValueError("Client returned invalid format. Expected JSON!") + except RPCError as err: + raise err + + return ret["result"] # Return only the result + + def login(self) : + r = self.rpcexec({ + "method": "call", + "params": [1, "login", [self.username, self.password]], + "jsonrpc": "2.0", + "id": 0 + }) + assert r, UnauthorizedError("Invalid login credentials!") + return r + + """ + Meta: Map all methods to RPC calls and pass through the arguments and result + """ + def __getattr__(self, name) : + def method(*args): + r = self.rpcexec({ + "method": "call", + "params": [0, name, [args]], + "jsonrpc": "2.0", + "id": 0 + }) + return r + return method + +if __name__ == '__main__': + rpc = GrapheneClient("localhost", 8090, "","") + print(json.dumps(rpc.login(),indent=4)) + print(json.dumps(rpc.get_accounts("1.2.0"),indent=4)) diff --git a/graphenelib/market.py b/graphenelib/market.py new file mode 100644 index 00000000..b1e8f6af --- /dev/null +++ b/graphenelib/market.py @@ -0,0 +1,247 @@ +from math import log10 + +class market : + def __init__(self, client) : + self.client = client ## pass rpc commands to the client + + def get_asset_id(self, asset): + return self.client.blockchain_get_asset(asset)["result"]["id"] + + def get_precision(self, asset): + return float(self.client.blockchain_get_asset(asset)["result"]["precision"]) + + def get_centerprice(self, quote, base): + return float(self.client.blockchain_market_status(quote, base)["result"]["center_price"]["ratio"]) + + def get_lowest_ask(self, asset1, asset2): + return float(self.client.blockchain_market_order_book(asset1, asset2)["result"][1][0]["market_index"]["order_price"]["ratio"]) + + def get_lowest_bid(self, asset1, asset2): + return float(self.client.blockchain_market_order_book(asset1, asset2)["result"][0][0]["market_index"]["order_price"]["ratio"]) + + def cancel_bids_less_than(self, account, quote, base, price): + cancel_args = self.get_bids_less_than(account, quote, base, price)[0] + response = self.client.batch("wallet_market_cancel_order", cancel_args) + return cancel_args + + def get_median(self, asset): + response = self.client.blockchain_get_feeds_for_asset(asset) + feeds = response["result"] + return feeds[len(feeds)-1]["median_price"] + + def cancel_bids_out_of_range(self, account, quote, base, price, tolerance): + cancel_args = self.get_bids_out_of_range(account, quote, base, price, tolerance)[0] + response = self.client.request("batch", ["wallet_market_cancel_order", cancel_args]) + return cancel_args + + def cancel_asks_out_of_range(self, account, quote, base, price, tolerance): + cancel_args = self.get_asks_out_of_range(account, quote, base, price, tolerance)[0] + response = self.client.request("batch", ["wallet_market_cancel_order", cancel_args]) + return cancel_args + + def get_balance(self, account, asset): + asset_id = self.get_asset_id(asset) + response = self.client.wallet_account_balance(account, asset) + if not response: + return 0 + if "result" not in response or response["result"] == None or not len(response["result"]): + return 0 + asset_array = response["result"][0][1] + amount = 0 + for item in asset_array: + if item[0] == asset_id: + amount = float(item[1]) + return amount / self.get_precision(asset) + return 0 + + def cancel_all_orders(self, account, quote, base): + cancel_args = self.get_all_orders(account, quote, base) + for i in cancel_args[0] : + response = self.client.wallet_market_cancel_order(i) + return cancel_args[1] + + def ask_at_market_price(self, name, amount, base, quote, confirm=False) : + response = self.client.blockchain_market_order_book(quote, base) + quotePrecision = self.get_precision(quote) + basePrecision = self.get_precision(base) + orders = [] + for order in response["result"][0]: # bid orders + order_price = float(order["market_index"]["order_price"]["ratio"])*(basePrecision / quotePrecision) + order_amount = float(order["state"]["balance"]/quotePrecision) / order_price # denoted in BASE + if amount >= order_amount : # buy full amount + orders.append([name, order_amount, base, order_price, quote]) + amount -= order_amount + elif amount < order_amount: # partial + orders.append([name, amount, base, order_price, quote]) + break + for o in orders : + print( "Selling %15.8f %s for %12.8f %s @ %12.8f" %(o[1], o[2], o[1]*o[3], o[4], o[3]) ) + if not confirm or self.client.query_yes_no( "I dare you confirm the orders above: ") : + for o in orders : + self.submit_ask(o[0], o[1], o[2], o[3], o[4]) + + def bid_at_market_price(self, name, amount, base, quote, confirm=False) : + response = self.client.blockchain_market_order_book(quote, base) + quotePrecision = self.get_precision(quote) + basePrecision = self.get_precision(base) + orders = [] + for order in response["result"][1]: # ask orders + order_price = float(order["market_index"]["order_price"]["ratio"])*(basePrecision / quotePrecision) + order_amount = float(order["state"]["balance"]/quotePrecision) / order_price # denoted in BASE + if amount >= order_amount : # buy full amount + orders.append([name, order_amount, base, order_price, quote]) + amount -= order_amount + elif amount < order_amount: # partial + orders.append([name, amount, base, order_price, quote]) + break + for o in orders : + print( "Selling %15.8f %s for %12.8f %s @ %12.8f" %(o[1], o[2], o[1]*o[3], o[4], o[3]) ) + if not confirm or self.client.query_yes_no( "I dare you confirm the orders above: ") : + for o in orders : + self.submit_bid(o[0], o[1], o[2], o[3], o[4]) + + def ask_limit(self, name, amount, base, quote, price_limit, confirm=False) : + print("Buying orders with price limit: %f %s/%s" % (price_limit, base, quote)) + response = self.client.blockchain_market_list_bids(quote, base) + quotePrecision = self.get_precision(quote) + basePrecision = self.get_precision(base) + orders = [] + for order in response["result"] : + order_price = float(order["market_index"]["order_price"]["ratio"])*(basePrecision / quotePrecision) + order_amount = float(order["state"]["balance"]/quotePrecision) / order_price # denoted in BASE + if order_price < price_limit : + orders.append([name, amount, base, price_limit, quote]) + break + else: + if amount >= order_amount : # buy full amount + orders.append([name, order_amount, base, order_price, quote]) + amount -= order_amount + elif amount < order_amount: # partial + orders.append([name, amount, base, order_price, quote]) + break + for o in orders : + print( "Buying %15.8f %s for %12.8f %s @ %12.8f" %(o[1], o[2], o[1]*o[3], o[4], o[3]) ) + if not confirm or self.client.query_yes_no( "I dare you confirm the orders above: ") : + for o in orders : + amount = str('%.*f' %(int(log10(quotePrecision)), o[1])) + self.submit_ask(o[0], amount, o[2], o[3], o[4]) + + def bid_limit(self, name, amount, base, quote, price_limit, confirm=False) : + print("Sell orders with price limit: %f %s/%s" % (price_limit, base, quote)) + response = self.client.blockchain_market_list_asks(quote, base) + quotePrecision = self.get_precision(quote) + basePrecision = self.get_precision(base) + orders = [] + for order in response["result"] : + order_price = float(order["market_index"]["order_price"]["ratio"])*(basePrecision / quotePrecision) + order_amount = float(order["state"]["balance"])/order_price/quotePrecision # denoted in BASE + if order_price > price_limit : + orders.append([name, amount, base, price_limit, quote]) + break + else: + if amount >= order_amount : # buy full amount + orders.append([name, order_amount, base, order_price, quote]) + amount -= order_amount + elif amount < order_amount: # partial + orders.append([name, amount, base, order_price, quote]) + break + for o in orders : + print( "Buying %15.8f %s for %12.8f %s @ %12.8f" %(o[1], o[2], o[1]*o[3], o[4], o[3]) ) + if not confirm or self.client.query_yes_no( "I dare you confirm the orders above: ") : + for o in orders : + amount = str('%.*f' %(int(log10(quotePrecision)), o[1])) + self.submit_bid(o[0], amount, o[2], o[3], o[4]) + + + def submit_bid(self, account, amount, quote, price, base): + print("%s submitted a bid" % account) + self.client.bid(account, amount, quote, price, base) + + def submit_ask(self, account, amount, quote, price, base): + print("%s submitted an ask" % account) + self.client.ask(account, amount, quote, price, base) + + def get_bids_less_than(self, account, quote, base, price): + quotePrecision = self.get_precision( quote ) + basePrecision = self.get_precision( base ) + response = self.client.wallet_market_order_list(quote, base, -1, account) + order_ids = [] + quote_shares = 0 + if "result" not in response or response["result"] == None: + return [[], 0] + for pair in response["result"]: + order_id = pair[0] + item = pair[1] + if item["type"] == "bid_order": + if float(item["market_index"]["order_price"]["ratio"])* (basePrecision / quotePrecision) < price: + order_ids.append(order_id) + quote_shares += int(item["state"]["balance"]) + print("%s canceled an order: %s" % (account, str(item))) + cancel_args = [item for item in order_ids] + return [cancel_args, float(quote_shares) / quotePrecision] + + def get_bids_out_of_range(self, account, quote, base, price, tolerance): + quotePrecision = self.get_precision( quote ) + basePrecision = self.get_precision( base ) + response = self.client.wallet_market_order_list(quote, base, -1, account) + order_ids = [] + quote_shares = 0 + if "result" not in response or response["result"] == None: + return [[], 0] + for pair in response["result"]: + order_id = pair[0] + item = pair[1] + if item["type"] == "bid_order": + if abs(price - float(item["market_index"]["order_price"]["ratio"]) * (basePrecision / quotePrecision)) > tolerance: + order_ids.append(order_id) + quote_shares += int(item["state"]["balance"]) + print("%s canceled an order: %s" % (account, str(item))) + cancel_args = [item for item in order_ids] + return [cancel_args, float(quote_shares) / quotePrecision] + + def get_asks_out_of_range(self, account, quote, base, price, tolerance): + quotePrecision = self.get_precision( quote ) + basePrecision = self.get_precision( base ) + response = self.client.wallet_market_order_list(quote, base, -1, account) + order_ids = [] + base_shares = 0 + if "result" not in response or response["result"] == None: + return [[], 0] + for pair in response["result"]: + order_id = pair[0] + item = pair[1] + if item["type"] == "ask_order": + if abs(price - float(item["market_index"]["order_price"]["ratio"]) * (basePrecision / quotePrecision)) > tolerance: + order_ids.append(order_id) + base_shares += int(item["state"]["balance"]) + cancel_args = [item for item in order_ids] + return [cancel_args, base_shares / basePrecision] + + def get_all_orders(self, account, quote, base): + response = self.client.wallet_market_order_list(quote, base, -1, account) + order_ids = [] + orders = [] + if "result" in response : + for item in response["result"]: + order_ids.append(item[0]) + orders.append(item[1]) + orderids = [item for item in order_ids] + return [ orderids, orders ] + return + + def get_last_fill (self, quote, base): + last_fill = -1 + response = self.client.blockchain_market_order_history(quote, base, 0, 1) + for order in response["result"]: + last_fill = float(order["ask_price"]["ratio"]) + return last_fill + + def get_price(self, quote, base): + response = self.client.blockchain_market_order_book(quote, base, 1) + order = response["result"] + quotePrecision = self.get_precision(quote) + basePrecision = self.get_precision(base) + lowest_bid = float(order[0][0]["market_index"]["order_price"]["ratio"])*(basePrecision / quotePrecision) + highest_ask = float(order[1][0]["market_index"]["order_price"]["ratio"])*(basePrecision / quotePrecision) + return (lowest_bid+highest_ask)/2 + diff --git a/graphenelib/paperwallet.py b/graphenelib/paperwallet.py new file mode 100644 index 00000000..68989ef5 --- /dev/null +++ b/graphenelib/paperwallet.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python +import csv +import qrcode +import qrcode.image.svg +import lxml.etree as et +from copy import deepcopy +import os +import graphenetools.bip38 as bip38 +import graphenetools.address as Address +import sys + +path = os.path.dirname(__file__) + +def paperwallet(wif, address, fronttext, asset, encrypt="", design="cass", backtext1=None, backtext2=None, backtext3=None, backQRcode=None) : + svgfront = path + "/paperwallet/designs/"+design+"-front.svg" + svgback = path + "/paperwallet/designs/"+design+"-back.svg" + ''' + Front Page + ''' + try : + fp = open(svgfront,'r') + except : + raise Exception("Cannot open SVG template file %s" % svgfront) + dom = et.parse(fp) + root = dom.getroot() + for layer in root.findall("./{http://www.w3.org/2000/svg}g") : + ## QRcode address + if layer.get("{http://www.inkscape.org/namespaces/inkscape}label") == "QRaddress" : + qr = qrcode.make(address, image_factory=qrcode.image.svg.SvgPathImage, error_correction=qrcode.constants.ERROR_CORRECT_L) # L,M,Q,H + l = layer.find("{http://www.w3.org/2000/svg}rect") + if l is not None : + x = float(l.get("x")) + y = float(l.get("y")) + scale = float(layer.find("{http://www.w3.org/2000/svg}rect").get("width"))/(qr.width+2*qr.border) + layer.set("transform","translate(%f,%f) scale(%f)" % (x,y,scale)) + layer.append(qr.make_path()) + layer.remove(layer.find("{http://www.w3.org/2000/svg}rect")) + ## QRcode privkey + if layer.get("{http://www.inkscape.org/namespaces/inkscape}label") == "QRprivkey" : + if encrypt : # (optionally) encrypt paper wallet + try : + encwif = bip38.bip38_encrypt(wif,encrypt) + except : + raise Exception("Error encoding the privkey for address %s. Already encrypted?" % address) + assert bip38.bip38_decrypt(encwif, encrypt) == wif, "Verification of encrypted privkey failed!" + qr = qrcode.make(encwif, image_factory=qrcode.image.svg.SvgPathImage, error_correction=qrcode.constants.ERROR_CORRECT_M) # L,M,Q,H + else : + qr = qrcode.make(wif, image_factory=qrcode.image.svg.SvgPathImage, error_correction=qrcode.constants.ERROR_CORRECT_M) # L,M,Q,H + + l = layer.find("{http://www.w3.org/2000/svg}rect") + if l is not None : + x = float(l.get("x")) + y = float(l.get("y")) + scale = float(layer.find("{http://www.w3.org/2000/svg}rect").get("width"))/(qr.width+2*qr.border) + layer.set("transform","translate(%f,%f) scale(%f)" % (x,y,scale)) + layer.append(qr.make_path()) + layer.remove(layer.find("{http://www.w3.org/2000/svg}rect")) + ## Address + if layer.get("{http://www.inkscape.org/namespaces/inkscape}label") == "textaddress" : + ## Verify that private keys is for given address + if Address.priv2btsaddr(Address.wif2hex(wif)) == address : + ltext = layer.find("{http://www.w3.org/2000/svg}text") + if ltext is not None : + l = ltext.find("{http://www.w3.org/2000/svg}tspan") + if l == None: + print("text area for plain text address not given in SVG") + else : + l.text = address + else : + raise Exception("Given address does not pair to given private key!") + if fronttext : + if layer.get("{http://www.inkscape.org/namespaces/inkscape}label") == "textamount" : + ltext = layer.find("{http://www.w3.org/2000/svg}text") + if ltext is not None : + l = ltext.find("{http://www.w3.org/2000/svg}tspan") + if l == None: + print("text area for fronttext not given in SVG") + else : + l.text = fronttext + ## Asset logo + if layer.get("{http://www.inkscape.org/namespaces/inkscape}label") == "assetlogo" : + if asset : + assetfile = path + "/paperwallet/BitAssets/bit%s-accepted-flat-square-2.svg" % asset.upper() + assetlogodom = et.parse(open(assetfile,'r')) + assetlogoroot = assetlogodom.getroot() + l = layer.find("{http://www.w3.org/2000/svg}rect") + if l is not None : + x = float(l.get("x")) + y = float(l.get("y")) + scale = float(layer.find("{http://www.w3.org/2000/svg}rect").get("width"))/float(assetlogoroot.get("width").split("px")[0]) + layer.set("transform","translate(%f,%f) scale(%f)" % (x,y,scale)) + layer.append(deepcopy(assetlogoroot)) + layer.remove(layer.find("{http://www.w3.org/2000/svg}rect")) + front = et.tostring(dom, pretty_print=True) + + ''' + Back Page + ''' + try : + fp = open(svgback,'r') + except : + raise Exception("Cannot open SVG template file %s" % svgback) + dom = et.parse(fp) + root = dom.getroot() + for layer in root.findall("./{http://www.w3.org/2000/svg}g") : + ## QRcode address + if backQRcode : + if layer.get("{http://www.inkscape.org/namespaces/inkscape}label") == "QRcode" : + qr = qrcode.make(backQRcode, image_factory=qrcode.image.svg.SvgPathImage, error_correction=qrcode.constants.ERROR_CORRECT_L) # L,M,Q,H + x = float(layer.find("{http://www.w3.org/2000/svg}rect").get("x")) + y = float(layer.find("{http://www.w3.org/2000/svg}rect").get("y")) + scale = float(layer.find("{http://www.w3.org/2000/svg}rect").get("width"))/(qr.width+2*qr.border) + layer.set("transform","translate(%f,%f) scale(%f)" % (x,y,scale)) + layer.append(qr.make_path()) + layer.remove(layer.find("{http://www.w3.org/2000/svg}rect")) + if backtext1 : + if layer.get("{http://www.inkscape.org/namespaces/inkscape}label") == "Text1" : + ltext = layer.find("{http://www.w3.org/2000/svg}text") + if ltext is not None : + if l == None: + print("text area for fronttext not given in SVG") + else : + l.text = backtext1 + if backtext2 : + if layer.get("{http://www.inkscape.org/namespaces/inkscape}label") == "Text2" : + ltext = layer.find("{http://www.w3.org/2000/svg}text") + if ltext is not None : + if l == None: + print("text area for fronttext not given in SVG") + else : + l.text = backtext2 + if backtext3 : + if layer.get("{http://www.inkscape.org/namespaces/inkscape}label") == "Text3" : + ltext = layer.find("{http://www.w3.org/2000/svg}text") + if ltext is not None : + if l == None: + print("text area for fronttext not given in SVG") + else : + l.text = backtext3 + back = et.tostring(dom, pretty_print=True) + + return front, back + +if __name__ == '__main__': + wif = Address.newwif() + address = Address.wif2btsaddr(wif) + front,back = paperwallet(wif, address, "front text", "USD", encrypt=None, design="cass", backtext1="Text1", backtext2="text2", backtext3="text3", backQRcode="http://delegate.xeroc.org") + print(str(back)) diff --git a/graphenelib/paperwallet/BitAssets/bitBTC-accepted-flat-square-2.svg b/graphenelib/paperwallet/BitAssets/bitBTC-accepted-flat-square-2.svg new file mode 100644 index 00000000..bb311601 --- /dev/null +++ b/graphenelib/paperwallet/BitAssets/bitBTC-accepted-flat-square-2.svg @@ -0,0 +1,49 @@ + + + + bitBTC-accepted-flat-square-2 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/graphenelib/paperwallet/BitAssets/bitCNY-accepted-flat-square-2.svg b/graphenelib/paperwallet/BitAssets/bitCNY-accepted-flat-square-2.svg new file mode 100644 index 00000000..8d9e2615 --- /dev/null +++ b/graphenelib/paperwallet/BitAssets/bitCNY-accepted-flat-square-2.svg @@ -0,0 +1,42 @@ + + + + bitCNY-accepted-flat-square-2 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/graphenelib/paperwallet/BitAssets/bitEUR-accepted-flat-square-2.svg b/graphenelib/paperwallet/BitAssets/bitEUR-accepted-flat-square-2.svg new file mode 100644 index 00000000..2eb9137d --- /dev/null +++ b/graphenelib/paperwallet/BitAssets/bitEUR-accepted-flat-square-2.svg @@ -0,0 +1,42 @@ + + + + bitEUR-accepted-flat-square-2 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/graphenelib/paperwallet/BitAssets/bitGOLD-accepted-flat-square-2.svg b/graphenelib/paperwallet/BitAssets/bitGOLD-accepted-flat-square-2.svg new file mode 100644 index 00000000..21c46b6a --- /dev/null +++ b/graphenelib/paperwallet/BitAssets/bitGOLD-accepted-flat-square-2.svg @@ -0,0 +1,42 @@ + + + + bitGOLD-accepted-flat-square-2 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/graphenelib/paperwallet/BitAssets/bitSILVER-accepted-flat-square-2.svg b/graphenelib/paperwallet/BitAssets/bitSILVER-accepted-flat-square-2.svg new file mode 100644 index 00000000..d0ebb1e9 --- /dev/null +++ b/graphenelib/paperwallet/BitAssets/bitSILVER-accepted-flat-square-2.svg @@ -0,0 +1,49 @@ + + + + bitSILVER-accepted-flat-square-2 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/graphenelib/paperwallet/BitAssets/bitUSD-accepted-flat-square-2.svg b/graphenelib/paperwallet/BitAssets/bitUSD-accepted-flat-square-2.svg new file mode 100644 index 00000000..5a9b8d94 --- /dev/null +++ b/graphenelib/paperwallet/BitAssets/bitUSD-accepted-flat-square-2.svg @@ -0,0 +1,49 @@ + + + + bitUSD-accepted-flat-square-2 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/graphenelib/paperwallet/designs/cass-adaptive-back.svg b/graphenelib/paperwallet/designs/cass-adaptive-back.svg new file mode 100644 index 00000000..7b851a3c --- /dev/null +++ b/graphenelib/paperwallet/designs/cass-adaptive-back.svg @@ -0,0 +1,5763 @@ + + + + + + image/svg+xml + + + + + + + Paperwallet back + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Android + + + IOS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed + + + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed + + + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed + + + + + + 1 + + + 2 + + + 3 + + diff --git a/graphenelib/paperwallet/designs/cass-back.svg b/graphenelib/paperwallet/designs/cass-back.svg new file mode 100644 index 00000000..b9fb1a26 --- /dev/null +++ b/graphenelib/paperwallet/designs/cass-back.svg @@ -0,0 +1,731 @@ + + + + Paperwallet back + Created with Sketch. + + + + + + + + + + + + + 1 + + + 2 + + + 3 + + + Escanee con su celular el codigo QR correspondiente a su sistema + operativo para instalar la BitWallet. + + Abra la aplicacion BitWallet. Vaya al home y despues de hacer click en + el icono de arriba a la derecha escanee la clave privada con su celular. + + BitWallet le mostrara el saldo de la misma y debera confirmar que la + clave sea barrida a su billetera, una transferencia interna, para tener + acceso a los fondos desde su telefono. + + + + + Android + + + IOS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + USD + + + bit + + + + + + + + + + + + + + + + 1 + + + 2 + + + 3 + + + + + + \ No newline at end of file diff --git a/graphenelib/paperwallet/designs/cass-es-back.svg b/graphenelib/paperwallet/designs/cass-es-back.svg new file mode 100644 index 00000000..adc07d21 --- /dev/null +++ b/graphenelib/paperwallet/designs/cass-es-back.svg @@ -0,0 +1,732 @@ + + + + Paperwallet back + Created with Sketch. + + + + + + + + + + + + + 1 + + + 2 + + + 3 + + + Escanee con su celular el codigo QR correspondiente a su sistema + operativo para instalar la BitWallet. + + Abra la aplicacion BitWallet. Vaya al home y despues de hacer click en + el icono de arriba a la derecha escanee la clave privada con su celular. + + BitWallet le mostrara el saldo de la misma y debera confirmar que la + clave sea barrida a su billetera, una transferencia interna, para tener + acceso a los fondos desde su telefono. + + + + + Android + + + IOS + + + + + 1 + + + 2 + + + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + USD + + + bit + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/graphenelib/paperwallet/designs/cass-es-front.svg b/graphenelib/paperwallet/designs/cass-es-front.svg new file mode 100644 index 00000000..82eb889a --- /dev/null +++ b/graphenelib/paperwallet/designs/cass-es-front.svg @@ -0,0 +1,4573 @@ + + + + + + image/svg+xml + + + + + + + Paperwallet front + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/graphenelib/paperwallet/designs/cass-front.svg b/graphenelib/paperwallet/designs/cass-front.svg new file mode 100644 index 00000000..307d1ec1 --- /dev/null +++ b/graphenelib/paperwallet/designs/cass-front.svg @@ -0,0 +1,4712 @@ + + + + + + image/svg+xml + + + + + + + Paperwallet front + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PUBLIC KEY + LOAD&VERIFY + + + SPEND&WITHDRAW + PRIVATE KEY + + + + + + + + + + + + + + + + + diff --git a/graphenelib/paperwallet/designs/xeroc-businesscard-back.svg b/graphenelib/paperwallet/designs/xeroc-businesscard-back.svg new file mode 100644 index 00000000..b8499630 --- /dev/null +++ b/graphenelib/paperwallet/designs/xeroc-businesscard-back.svg @@ -0,0 +1,58 @@ + + + + + + + image/svg+xml + + Paperwallet front + + + + + Paperwallet back + Created with Sketch. + diff --git a/graphenelib/paperwallet/designs/xeroc-businesscard-front.svg b/graphenelib/paperwallet/designs/xeroc-businesscard-front.svg new file mode 100644 index 00000000..64b4b6d8 --- /dev/null +++ b/graphenelib/paperwallet/designs/xeroc-businesscard-front.svg @@ -0,0 +1,9795 @@ + + + + + + + image/svg+xml + + Paperwallet front + + + + + Paperwallet front + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BTSNc9xDSwXT4dtG2ype5DrJqnRfswe8xm4A + + diff --git a/graphenelib/paperwallet/designs/xeroc-sharedrop-dina5-back.svg b/graphenelib/paperwallet/designs/xeroc-sharedrop-dina5-back.svg new file mode 100644 index 00000000..b8499630 --- /dev/null +++ b/graphenelib/paperwallet/designs/xeroc-sharedrop-dina5-back.svg @@ -0,0 +1,58 @@ + + + + + + + image/svg+xml + + Paperwallet front + + + + + Paperwallet back + Created with Sketch. + diff --git a/graphenelib/paperwallet/designs/xeroc-sharedrop-dina5-front.svg b/graphenelib/paperwallet/designs/xeroc-sharedrop-dina5-front.svg new file mode 100644 index 00000000..1e4f6aea --- /dev/null +++ b/graphenelib/paperwallet/designs/xeroc-sharedrop-dina5-front.svg @@ -0,0 +1,6609 @@ + + + + + + image/svg+xml + + Paperwallet front + + + + + + Paperwallet front + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PUBLIC KEY + LOAD&VERIFY + + + + + + + + + + + + + + + + + Introcuction + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BitShares 101 + + r/BitShares + + f/BitShares + + Twitter + + Google+ + + Whitepaper + + BitSharesTalk + + Github + + Web Wallet + + Wiki + + Block Explorer + + Free Samples + + Meetup + + BitShares.tv + + Youtube + MetaExchange + + Blocktraders + + Cryptofresh + + Cryptosmith + + + PRIVATE KEY + SPEND + + BeyondBitcoin + + + Mobile Wallet + + Triberr + + AboutBTS + General + TechnicalInformation + Community + Services + SocialNetworks + + and more ... + + Sharedrop + Wallet + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/graphenelib/transactions.py b/graphenelib/transactions.py new file mode 100644 index 00000000..0b2fcf76 --- /dev/null +++ b/graphenelib/transactions.py @@ -0,0 +1,561 @@ +import time +from calendar import timegm +from datetime import datetime +import json +import struct +from binascii import hexlify, unhexlify +import hashlib +import math +import ecdsa +import sys +from pprint import pprint +import time +from copy import copy + +#import graphenelib.address as address +#from graphenelib.base58 import base58decode,base58encode,base58CheckEncode,base58CheckDecode,btsBase58CheckEncode,btsBase58CheckDecode + +def varint(n): + data = b'' + while n >= 0x80: + data += bytes([ (n & 0x7f) | 0x80 ]) + n >>= 7 + data += bytes([n]) + return data + +def varintdecode(data): + shift = 0 + result = 0 + for c in data: + b = ord(c) + result |= ((b & 0x7f) << shift) + if not (b & 0x80): + break + shift += 7 + return result + +def varintlength(n): + length = 1 + while n >= 0x80: + length += 1 + n >>= 7 + return length + +def variable_buffer( s ) : + return varint(len(s)) + s + +object_type = {} +object_type["null"] = 0 +object_type["base"] = 1 +object_type["key"] = 2 +object_type["account"] = 3 +object_type["asset"] = 4 +object_type["force_settlement"] = 5 +object_type["delegate"] = 6 +object_type["witness"] = 7 +object_type["limit_order"] = 8 +object_type["short_order"] = 9 +object_type["call_order"] = 10 +object_type["custom"] = 11 +object_type["proposal"] = 12 +object_type["operation_history"] = 13 +object_type["withdraw_permission"] = 14 +object_type["bond_offer"] = 15 +object_type["bond"] = 16 +object_type["file"] = 17 +object_type["OBJECT_TYPE_COUNT"] = 18 + +operations = {} +operations["transfer"] = 0 +operations["limit_order_create"] = 1 +operations["short_order_create"] = 2 +operations["limit_order_cancel"] = 3 +operations["short_order_cancel"] = 4 +operations["call_order_update"] = 5 +operations["key_create"] = 6 +operations["account_create"] = 7 +operations["account_update"] = 8 +operations["account_whitelist"] = 9 +operations["account_transfer"] = 10 +operations["asset_create"] = 11 +operations["asset_update"] = 12 +operations["asset_update_bitasset"] = 13 +operations["asset_update_feed_producers"] = 14 +operations["asset_issue"] = 15 +operations["asset_burn"] = 16 +operations["asset_fund_fee_pool"] = 17 +operations["asset_settle"] = 18 +operations["asset_global_settle"] = 19 +operations["asset_publish_feed"] = 20 +operations["delegate_create"] = 21 +operations["witness_create"] = 22 +operations["witness_withdraw_pay"] = 23 +operations["proposal_create"] = 24 +operations["proposal_update"] = 25 +operations["proposal_delete"] = 26 +operations["withdraw_permission_create"] = 27 +operations["withdraw_permission_update"] = 28 +operations["withdraw_permission_claim"] = 29 +operations["withdraw_permission_delete"] = 30 +operations["fill_order"] = 31 +operations["global_parameters_update"] = 32 +operations["file_write"] = 33 +operations["vesting_balance_create"] = 34 +operations["vesting_balance_withdraw"] = 35 +operations["bond_create_offer"] = 36 +operations["bond_cancel_offer"] = 37 +operations["bond_accept_offer"] = 38 +operations["bond_claim_collateral"] = 39 +operations["worker_create"] = 40 +operations["custom"] = 41 + +reserved_spaces = {} +reserved_spaces["relative_protocol_ids"] = 0 +reserved_spaces["protocol_ids"] = 1 +reserved_spaces["implementation_ids"] = 2 +reserved_spaces["RESERVE_SPACES_COUNT"] = 3 + +chainid = "75c11a81b7670bbaa721cc603eadb2313756f94a3bcbb9928e9101432701ac5f" +PREFIX = "BTS" + + +# Format C Type Python type Standard size Notes +# x pad byte no value +# c char string of length 1 1 +# b signed char integer 1 (3) +# B unsigned char integer 1 (3) +# ? _Bool bool 1 (1) +# h short integer 2 (3) +# H unsigned short integer 2 (3) +# i int integer 4 (3) +# I unsigned int integer 4 (3) +# l long integer 4 (3) +# L unsigned long integer 4 (3) +# q long long integer 8 (2), (3) +# Q unsigned long long integer 8 (2), (3) +# f float float 4 (4) +# d double float 8 (4) +# s char[] string +# p char[] string +# P void* integer (5), (3) + +""" +Variable types +""" +class Varint32() : + def __init__(self,d) : self.data = d + def __bytes__(self) : return varint(self.data) + def __str__(self) : return '%d' % self.data + +class Uint64(object) : + def __init__(self,d) : self.data = d + def __bytes__(self) : return struct.pack("> 31) +# def encodeZigZag64(self,n): return (n << 1) ^ (n >> 63) +# def decodeZigZag32(self,n): return (n >> 1) ^ -(n & 1) +# def decodeZigZag64(self,n): return (n >> 1) ^ -(n & 1) +# +# def __init__( self, asset_id, slate_id, condition_type, condition ) : +# self.asset_id = asset_id +# self.slate_id = slate_id +# self.type = condition_type +# self.data = condition +# def towire(self) : +# wire = varint(self.encodeZigZag32(self.asset_id)) +# wire += struct.pack("