├── MANIFEST.in ├── vippool ├── __init__.py └── storage.py ├── tests └── test.py ├── README.md ├── LICENSE ├── README.en.md ├── sample.py ├── setup.py └── .gitignore /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include README.en.md 3 | -------------------------------------------------------------------------------- /vippool/__init__.py: -------------------------------------------------------------------------------- 1 | #========================================================# 2 | # # 3 | # __init__.py - 今は特に何もしない # 4 | # # 5 | # (C) 2019-2021 VIPPOOL Inc. # 6 | # # 7 | #========================================================# 8 | -------------------------------------------------------------------------------- /tests/test.py: -------------------------------------------------------------------------------- 1 | #========================================================# 2 | # # 3 | # test.py - vippool_storage 動作テスト # 4 | # # 5 | # (C) 2019-2021 VIPPOOL Inc. # 6 | # # 7 | #========================================================# 8 | 9 | # ライブラリのインポート 10 | import sys 11 | sys.path.append( '..' ) 12 | from vippool import storage 13 | 14 | # ECDSA のテスト 15 | storage.ECDSA.selfTest() 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vippool-storage 2 | 3 | English: [README.en.md](README.en.md) 4 | 5 | このプロジェクトは、株式会社 VIPPOOL が開発した 6 | vippool-clerk サービスへアクセスすることで、 7 | Python から簡単にモナコインのブロックチェーンへの 8 | データ書き込み・読み込みができるライブラリです。 9 | 10 | 現在は vippool-clerk サービスの運用を行っておりませんので、 11 | 各自によりサーバを立ち上げる必要があります。 12 | 13 | ## できること 14 | 15 | 1. 秘密鍵に対応するコインアドレスの、現在の残高を知ることが出来ます。 16 | 2. 秘密鍵に対応するコインアドレスから、別のコインアドレスへ送金することができます。 17 | 3. ブロックチェーンに任意のデータ列を書き込むことができます。(別途トランザクション手数料が必要です)。 18 | 4. ブロックチェーンに書き込まれた任意データを読み込むことができます。 19 | 20 | ## インストール方法 21 | 22 | pip を用いてインストールが可能です。 23 | 24 | > $ pip install vippool_storage 25 | 26 | ## 使用方法 27 | 28 | リポジトリの [sample.py](sample.py) をご覧ください。 29 | 30 | ## 連絡先 31 | 32 | お問い合わせ、ご要望、バグ報告等は、github の issue へお気軽にどうぞ。 33 | https://github.com/vippool/storage/issues 34 | 35 | もしくは、開発チームまでメールいただいても構いません。 36 | dev-team@vippool.net 37 | 38 | ## ライセンス 39 | 40 | (C) 2019-2022 VIPPOOL Inc. 41 | 42 | このプロジェクトは、MIT ライセンスで提供されます。 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2021 vippool 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | # vippool-storage 2 | 3 | This project provides a library to access the vippool-clerk service, which was run by VIPPOOL Corp., 4 | and let you use Python to easily write to and read from the MonaCoin blockchain. 5 | 6 | The vippool-clerk service is not in operation at this time. 7 | You will need to set up your own server. 8 | 9 | ## Features 10 | 11 | 1. Consult the current balance of a coin-address for your private key. 12 | 2. Send coins to some other coin-address, from a coin-address for your private key. 13 | 3. Write arbitrary data to the blockchain. (requires a separate transaction fee) 14 | 4. Read arbitrary data from the blockchain. 15 | 16 | ## Installing 17 | 18 | You can use pip to install the python package: 19 | > $ pip install vippool_storage 20 | 21 | ## Usage 22 | 23 | Please see the file [sample.py](sample.py) as an example. 24 | 25 | ## Contact 26 | 27 | For asking for information, request features, or submit bug reports; please use the Github "issues" page. 28 | https://github.com/vippool/storage/issues 29 | 30 | You can also send an email to our developers: 31 | dev-team@vippool.net 32 | 33 | ## License 34 | 35 | (C) 2019-2022 VIPPOOL Inc. 36 | 37 | This project is published under the MIT license. 38 | -------------------------------------------------------------------------------- /sample.py: -------------------------------------------------------------------------------- 1 | #========================================================# 2 | # # 3 | # sample.py - サンプルプログラム # 4 | # # 5 | # (C) 2019-2021 VIPPOOL Inc. # 6 | # # 7 | #========================================================# 8 | 9 | import os 10 | from vippool.storage import vippool_storage 11 | 12 | # 新規にコインアドレスを作成する 13 | vs = vippool_storage( coind_type = 'monacoind_test' ) 14 | print( vs.privKey() ) # 秘密鍵を取得 15 | print( vs.address() ) # コインアドレスを取得 16 | 17 | # 既にある秘密鍵からインスタンスを生成 18 | if os.getenv( 'PRIVKEY', None ) is None: 19 | vs = vippool_storage( privKey = '74657a79fd323d5072ca81c6b99e2ffb5f0735d16fd5963289ba6f837c0413ef' ) 20 | else: 21 | vs = vippool_storage( 'monacoind_test', os.getenv( 'PRIVKEY' ) ) 22 | print( vs.privKey() ) 23 | print( vs.address() ) 24 | print( vs.balance() ) # 残高取得 25 | #print( vs.send( 'mt5xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 1.0, 0.01 ) ) # 1.0 MONA を手数料 0.01 MONA で送金 26 | #print( vs.write( 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 0.01 ) ) # 手数料 0.01 MONA で任意データ書き込み 27 | print( vs.read( '39a6c92e1fc13406b23e53812a242e7b7253b948135c5b58db9013dce8241b9a' ) ) # 書いたデータを読み込む 28 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #========================================================# 2 | # # 3 | # setup.py - PyPI 用 setup スクリプト # 4 | # # 5 | # (C) 2019-2021 VIPPOOL Inc. # 6 | # # 7 | #========================================================# 8 | 9 | from setuptools import setup, find_packages 10 | from os import path 11 | from io import open 12 | 13 | here = path.abspath( path.dirname( __file__ ) ) 14 | 15 | with open( path.join( here, 'README.en.md' ), encoding = 'utf-8' ) as f: 16 | long_description = f.read() 17 | 18 | setup( 19 | name = 'vippool_storage', 20 | version = '1.1.6', 21 | description = 'A simple interface for the block chain', 22 | long_description = long_description, 23 | long_description_content_type = 'text/markdown', 24 | url = 'https://github.com/vippool/storage', 25 | author = 'VIPPOOL Inc.', 26 | author_email = 'dev-team@vippool.net', 27 | classifiers = [ 28 | 'Intended Audience :: Developers', 29 | 'License :: OSI Approved :: MIT License', 30 | 'Programming Language :: Python :: 2', 31 | 'Programming Language :: Python :: 2.7', 32 | 'Programming Language :: Python :: 3', 33 | 'Programming Language :: Python :: 3.5', 34 | 'Programming Language :: Python :: 3.8', 35 | ], 36 | keywords = 'blockchain', 37 | packages = find_packages( exclude = [ 'tests' ] ), 38 | install_requires = [ 39 | 'future', 40 | 'six', 41 | ], 42 | ) 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /vippool/storage.py: -------------------------------------------------------------------------------- 1 | #========================================================# 2 | # # 3 | # vippool/storage.py - 暗号資産送金・任意データ読み書き # 4 | # # 5 | # (C) 2019-2021 VIPPOOL Inc. # 6 | # # 7 | #========================================================# 8 | 9 | from builtins import int 10 | from binascii import hexlify 11 | from binascii import unhexlify 12 | from os import urandom 13 | from six.moves.urllib.parse import urlencode 14 | from future.builtins.misc import pow 15 | import http.client 16 | import json 17 | 18 | # secp256k1 曲線のパラメータ 19 | ec_prm_l = 256 20 | ec_prm_p = int( 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F', 16 ) 21 | ec_prm_a = int( '0000000000000000000000000000000000000000000000000000000000000000', 16 ) 22 | ec_prm_b = int( '0000000000000000000000000000000000000000000000000000000000000007', 16 ) 23 | ec_prm_n = int( 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16 ) 24 | ec_point_g_x = int( '79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798', 16 ) 25 | ec_point_g_y = int( '483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8', 16 ) 26 | 27 | # モンゴメリ乗算を行うためのパラメータ 28 | mng_prm_d = int( 'C9BD1905155383999C46C2C295F2B761BCB223FEDC24A059D838091DD2253531', 16 ) 29 | mng_prm_s = int( '1000007a2000e90a1', 16 ) 30 | mng_prm_m = int( 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16 ) 31 | 32 | # 曲線の係数体上で非平方数となる値の1例 33 | sqrt_b = int( 3 ) 34 | 35 | # API サーバ 36 | api_host = 'clerk.vippool.net' 37 | api_path = '/api/v1/%s' 38 | 39 | # x の y に対する逆元を求める 40 | def inverse( x, y ): 41 | r0 = x 42 | r1 = y 43 | a0 = 1 44 | a1 = 0 45 | 46 | # 拡張ユークリッドの互除法 47 | while r1 > 0: 48 | q1 = r0 // r1 49 | r2 = r0 % r1 50 | a2 = a0 - q1 * a1 51 | r0 = r1 52 | r1 = r2 53 | a0 = a1 54 | a1 = a2 55 | 56 | if r0 < 0: 57 | r0 = r0 + y 58 | 59 | return a0 60 | 61 | 62 | # x の GF(y) 上での平方根を求める 63 | def sqrt( x, y ): 64 | # 位数 - 1 から 2 冪の値を分離する 65 | s = 0 66 | t = y - 1 67 | while (t & 1) == 0: 68 | t = t >> 1 69 | s = s + 1 70 | 71 | # 適当な非平方数の t 乗を求める 72 | b = pow( sqrt_b, t, y ) 73 | 74 | # x の逆元を求める 75 | xi = inverse( x, y ) 76 | 77 | # x の (t+1)/2 乗を求める 78 | r = pow( x, (t + 1) // 2, y ) 79 | 80 | for i in range( s - 2, -1, -1 ): 81 | n = int( 1 ) << i 82 | d = pow( r * r * xi, n, y ) 83 | if d != 1: 84 | r = r * b % y 85 | b = b * b % y 86 | 87 | return r 88 | 89 | 90 | # モンゴメリドメインでの値 91 | class montgomery: 92 | # モンゴメリ乗算 93 | @staticmethod 94 | def mng_mult( a, b ): 95 | t = a * b 96 | u = t & mng_prm_m 97 | u = (u * mng_prm_d) & mng_prm_m 98 | k = (t + u * ec_prm_p) >> ec_prm_l 99 | if k >= ec_prm_p: 100 | k = k - ec_prm_p 101 | return k 102 | 103 | # 通常の値からモンゴメリドメインに変換して格納する 104 | def __init__( self, x, conv = True ): 105 | if conv: 106 | self.x = self.mng_mult( x, mng_prm_s ) 107 | elif isinstance( x, montgomery ): 108 | self.x = x.x 109 | else: 110 | self.x = x 111 | 112 | # 元の値に戻して取得する 113 | def get( self ): 114 | return self.mng_mult( self.x, 1 ) 115 | 116 | # 加算 117 | def __add__( self, rhs ): 118 | t = self.x + rhs.x 119 | if t >= ec_prm_p: 120 | t = t - ec_prm_p 121 | return montgomery( t, False ) 122 | 123 | # 減算 124 | def __sub__( self, rhs ): 125 | t = self.x - rhs.x 126 | if t < 0: 127 | t = t + ec_prm_p 128 | return montgomery( t, False ) 129 | 130 | # 乗算 131 | def __mul__( self, rhs ): 132 | return montgomery( self.mng_mult( self.x, rhs.x ), False ) 133 | 134 | # 逆元を求める 135 | def inv( self ): 136 | t = inverse( self.x, ec_prm_p ) 137 | t = self.mng_mult( t, mng_prm_s ) 138 | return montgomery( t ) 139 | 140 | 141 | # 楕円曲線の座標クラス 142 | class ec_point: 143 | # アフィン座標からヤコビアン座標に変換して格納する 144 | def __init__( self, x, y, z = 1, conv = True ): 145 | self.coord_x = montgomery( x, conv ) 146 | self.coord_y = montgomery( y, conv ) 147 | self.coord_z = montgomery( z, conv ) 148 | self.mng_a = montgomery( ec_prm_a ) 149 | 150 | # アフィン座標に変換する 151 | def affine( self ): 152 | # z 座標の逆元を求める 153 | z_i = self.coord_z.inv() 154 | 155 | # z 座標の 2 乗 3 乗を求める 156 | z_i2 = z_i * z_i 157 | z_i3 = z_i * z_i2 158 | 159 | # モンゴメリドメインでの x, y 座標を求める 160 | x = self.coord_x * z_i2 161 | y = self.coord_y * z_i3 162 | 163 | # モンゴメリ表現から元に戻す 164 | return x.get(), y.get() 165 | 166 | # 2 倍算 167 | def double( self ): 168 | # 各座標値の 2 乗を計算 169 | x_2 = self.coord_x * self.coord_x 170 | y_2 = self.coord_y * self.coord_y 171 | z_2 = self.coord_z * self.coord_z 172 | 173 | # y, z は 4 乗も計算 174 | y_4 = y_2 * y_2 175 | z_4 = z_2 * z_2 176 | 177 | # x の 4 倍を求める 178 | x_x4 = self.coord_x + self.coord_x 179 | x_x4 = x_x4 + x_x4 180 | 181 | # x^2 の 3 倍を求める 182 | x_2_x3 = x_2 + x_2 183 | x_2_x3 = x_2 + x_2_x3 184 | 185 | # y^4 の 8 倍を求める 186 | y_4_x8 = y_4 + y_4 187 | y_4_x8 = y_4_x8 + y_4_x8 188 | y_4_x8 = y_4_x8 + y_4_x8 189 | 190 | # 2 倍算のテンポラリ値 191 | s = x_x4 * y_2 192 | m = self.mng_a * z_4 + x_2_x3 193 | m_2 = m * m 194 | 195 | # x, y, z 座標の計算 196 | x = m_2 - s - s 197 | y = m * (s - x) - y_4_x8 198 | z = self.coord_y * self.coord_z 199 | z = z + z 200 | 201 | return ec_point( x, y, z, False ) 202 | 203 | # 加算 204 | def __add__( self, rhs ): 205 | # ヤコビアン座標で同一点であれば 2 倍算を行う 206 | if self.coord_x == rhs.coord_x and self.coord_y == rhs.coord_y and self.coord_z == rhs.coord_z: 207 | return self.double() 208 | 209 | # 自身が無限遠点なら加算相手が結果そのままになる 210 | if self.coord_z.x == 0: 211 | return rhs 212 | 213 | # 相手が無限遠点なら何もしない 214 | if rhs.coord_z.x == 0: 215 | return self 216 | 217 | # それぞれの z 座標の 2 乗 3 乗を求める 218 | z1_2 = self.coord_z * self.coord_z 219 | z2_2 = rhs.coord_z * rhs.coord_z 220 | z1_3 = z1_2 * self.coord_z 221 | z2_3 = z2_2 * rhs.coord_z 222 | 223 | # 楕円曲線上の落とした時の座標の n 倍を求める 224 | u1 = self.coord_x * z2_2 225 | u2 = rhs.coord_x * z1_2 226 | s1 = self.coord_y * z2_3 227 | s2 = rhs.coord_y * z1_3 228 | 229 | if u1 == u2: 230 | if s1 == s2: 231 | # 楕円曲線上の落とした時に同じ点に落ちるならば 2 倍算 232 | return self.double() 233 | else: 234 | # 共役点の和は無限遠点 235 | return ec_point( 0, 0, 0 ) 236 | 237 | # 加算のテンポラリ値 238 | h = u2 - u1 239 | r = s2 - s1 240 | h_2 = h * h 241 | r_2 = r * r 242 | h_3 = h * h_2 243 | u1h2 = u1 * h_2 244 | 245 | # x, y, z 座標の計算 246 | x = r_2 - (h_3 + u1h2 + u1h2) 247 | y = (r * (u1h2 - x)) - (s1 * h_3) 248 | z = self.coord_z * rhs.coord_z * h 249 | 250 | return ec_point( x, y, z, False ) 251 | 252 | # 座標のスカラ倍を求める 253 | def scalar( self, lhs ): 254 | # 初期値は無限遠点 255 | r = ec_point( 0, 0, 0 ) 256 | 257 | # テンポラリ 258 | t = self 259 | 260 | # バイナリ法でスカラ倍を求める 261 | for i in range( 0, ec_prm_l ): 262 | tt = r + t 263 | if (lhs & 1) != 0: 264 | r = tt 265 | t = t + t 266 | lhs = lhs >> 1 267 | 268 | return r 269 | 270 | @staticmethod 271 | def selfTest(): 272 | p = ec_point( ec_point_g_x, ec_point_g_y ) 273 | 274 | # 位数をかけると無限遠点に飛ぶ 275 | r = p.scalar( ec_prm_n ) 276 | x, y = r.affine() 277 | print( 'x: %064x' % x ) 278 | print( 'y: %064x' % y ) 279 | if x == 0 and y == 0: 280 | print( 'ok.' ) 281 | else: 282 | print( 'ng.' ) 283 | 284 | # 適当な秘密鍵 285 | r = p.scalar( int( 'D2E85CC6AC3A6701040D7E9B57F1F24CD748A20626F06F2D5844059D024F5256', 16 ) ) 286 | x, y = r.affine() 287 | print( 'x: %064x' % x ) 288 | print( 'y: %064x' % y ) 289 | if x == int( 'D76F60853013746C8D0160CDCF2630309A2170D105FF6C96503F46A1A0BCC4D8', 16 ) and y == int( '0F9D1C3D8AC0C2D8C589A839E226D60FFD513B3941AC92DC20EDF6EF337BC4E0', 16 ): 290 | print( 'ok.' ) 291 | else: 292 | print( 'ng.' ) 293 | 294 | 295 | class ECDSA: 296 | def __init__( self, privKey = None ): 297 | if privKey is None: 298 | # [ 1, n-1 ] の範囲で乱数を生成する 299 | while True: 300 | k = int( hexlify( urandom( 32 ) ), 16 ) 301 | if k != 0 and k < ec_prm_n: 302 | break 303 | 304 | # 乱数を秘密鍵とする 305 | self.privKey = k 306 | else: 307 | # 引数で与えられた秘密鍵を使う 308 | self.privKey = privKey 309 | 310 | # ECDSA 公開鍵の作成 311 | def pubKey( self ): 312 | g = ec_point( ec_point_g_x, ec_point_g_y ) 313 | kg = g.scalar( self.privKey ) 314 | return kg.affine() 315 | 316 | # ECDSA 署名の作成 317 | def sign( self, hash ): 318 | g = ec_point( ec_point_g_x, ec_point_g_y ) 319 | 320 | while True: 321 | # [ 1, n-1 ] の範囲で乱数を生成する 322 | k = int( hexlify( urandom( 32 ) ), 16 ) 323 | k = (k + 1) % ec_prm_n 324 | 325 | # k * G を計算する 326 | kg = g.scalar( k ) 327 | x, y = kg.affine() 328 | 329 | # r を求める 330 | r = x % ec_prm_n 331 | if r == 0: 332 | continue 333 | 334 | # s を求める 335 | ki = inverse( k, ec_prm_n ) 336 | s = (ki * (hash + r * self.privKey)) % ec_prm_n 337 | if s == 0: 338 | continue 339 | 340 | return r, s 341 | 342 | # ECDSA 署名の検証 343 | @staticmethod 344 | def verify( hash, signature_r, signature_s, pubKeyX, pubKeyY ): 345 | # ヤコビアン座標を使っている都合上、無限遠点は ( 0, 0 ) になる 346 | if pubKeyX == 0 and pubKeyY == 0: 347 | return False 348 | 349 | # 公開鍵が楕円曲線上の点であることを確認する 350 | lhs = (pubKeyY * pubKeyY) % ec_prm_p 351 | rhs = (pubKeyX * pubKeyX * pubKeyX + ec_prm_a * pubKeyX + ec_prm_b) % ec_prm_p 352 | if lhs != rhs: 353 | return False 354 | 355 | # 位数を確認する 356 | q = ec_point( pubKeyX, pubKeyY ) 357 | nq = q.scalar( ec_prm_n ) 358 | if nq.coord_z.x != 0: 359 | return False 360 | 361 | # r と s の値域を確認する 362 | if signature_r < 1 or ec_prm_n <= signature_r: 363 | return False 364 | if signature_s < 1 or ec_prm_n <= signature_s: 365 | return False 366 | 367 | w = inverse( signature_s, ec_prm_n ) 368 | u1 = (hash * w) % ec_prm_n 369 | u2 = (signature_r * w) % ec_prm_n 370 | 371 | g = ec_point( ec_point_g_x, ec_point_g_y ) 372 | r = g.scalar( u1 ) + q.scalar( u2 ) 373 | x, y = r.affine() 374 | 375 | return (x % ec_prm_n) == signature_r 376 | 377 | # バイナリ配列の公開鍵から X, Y 座標を計算する 378 | @staticmethod 379 | def decompress( pubKey ): 380 | # エラーの場合は None を返す 381 | x = None 382 | y = None 383 | 384 | # uncompressed 形式の場合は X, Y 座標が連続している 385 | if pubKey[0] == 0x04 and len( pubKey ) == 65: 386 | x = int( hexlify( pubKey[1:33] ), 16 ) 387 | y = int( hexlify( pubKey[33:65] ), 16 ) 388 | 389 | # compressed 形式の場合は X 座標を取り出してから計算する 390 | if (pubKey[0] == 0x02 or pubKey[0] == 0x03) and len( pubKey ) == 33: 391 | x = int( hexlify( pubKey[1:33] ), 16 ) 392 | y_2 = x * x * x + ec_prm_a * x + ec_prm_b 393 | y = sqrt( y_2, ec_prm_p ) 394 | 395 | if (pubKey[0] & 1) != (y & 1): 396 | y = ec_prm_p - y 397 | 398 | return x, y 399 | 400 | # セルフテスト 401 | @staticmethod 402 | def selfTest(): 403 | ec_point.selfTest() 404 | 405 | for i in range( 0, 10 ): 406 | h = int( hexlify( urandom( 32 ) ), 16 ) 407 | k = ECDSA() 408 | pkx, pky = k.pubKey() 409 | 410 | # uncompressed 形式 411 | s = bytearray( unhexlify( '04%064X%064X' % (pkx, pky) ) ) 412 | if ECDSA.decompress( s ) != (pkx, pky): 413 | print( pkx, pky ) 414 | 415 | # compressed 形式 416 | s = bytearray( unhexlify( '%02X%064X' % (2 + (pky & 1), pkx) ) ) 417 | if ECDSA.decompress( s ) != (pkx, pky): 418 | print( pkx, pky ) 419 | 420 | # 署名作成と検証 421 | r, s = k.sign( h ) 422 | print( ECDSA.verify( h, r, s, pkx, pky ) ) 423 | 424 | # 公開する API クラス 425 | class vippool_storage: 426 | # API サーバにアクセスするメソッド 427 | def request( self, api, method, params ): 428 | path = api_path % api 429 | params['coind_type'] = self.coind_type 430 | params = urlencode( params ) 431 | 432 | conn = http.client.HTTPSConnection( api_host ) 433 | if method == 'GET': 434 | conn.request( method, path + '?' + params ) 435 | else: 436 | conn.request( method, path, params ) 437 | res = conn.getresponse() 438 | 439 | if res.status == 200: 440 | return json.loads( res.read().decode('UTF-8') ) 441 | else: 442 | raise Exception( res.status, res.read() ) 443 | 444 | # 初期化 445 | # - privKey を None にすると自動生成する 446 | def __init__( self, coind_type = 'monacoind', privKey = None ): 447 | if privKey is not None: 448 | if not isinstance( privKey, str ): 449 | raise TypeError( 'privKey' ) 450 | if len( privKey ) != 64: 451 | raise Exception( 'privKey', 'length' ) 452 | 453 | # privKey は 16 進文字列のはず 454 | # - パースできなければここから例外が飛ぶ 455 | privKey = int( privKey, 16 ) 456 | 457 | # ECDSA インスタンス作成 458 | self.ecdsa = ECDSA( privKey ) 459 | 460 | # 公開鍵を取得する 461 | x, y = self.ecdsa.pubKey() 462 | self.pubKey = '04%064X%064X' % (x, y) 463 | 464 | self.coind_type = coind_type 465 | 466 | # 秘密鍵を取得する 467 | # - 64 文字の 16 進数文字列で返す 468 | def privKey( self ): 469 | return '%064X' % self.ecdsa.privKey 470 | 471 | # API サーバに投げてコインアドレスに変換する 472 | def address( self ): 473 | return self.request( 'address', 'GET', { 'pub_key': self.pubKey } )['address'] 474 | 475 | # 残高確認 476 | def balance( self ): 477 | try: 478 | return self.request( 'balance', 'GET', { 'addresses': json.dumps( [ self.address() ] ), 'limit': 1 } )['balance'] 479 | except Exception as e: 480 | if e.args[0] == 404: 481 | return None 482 | else: 483 | raise 484 | 485 | # 送金トランザクション作成 486 | def newtx( self, to, value, fee, data ): 487 | # トランザクションの雛形作成 488 | params = { 489 | 'from': [ self.address() ], 490 | 'to': [ to ], 491 | 'req_sigs': 1, 492 | 'value': value, 493 | 'fee': fee 494 | } 495 | if data is not None: 496 | params['data'] = hexlify( data ).decode('UTF-8') 497 | res = self.request( 'preparetx', 'GET', { 'params': json.dumps( params ) } ) 498 | 499 | # 送金の電子署名を作成する 500 | sign = [] 501 | for e in res['sign']: 502 | r, s = self.ecdsa.sign( int( e['hash'], 16 ) ) 503 | sign.append( [ '%064X%064X' % (r, s) ] ) 504 | 505 | # 送金実行 506 | params = { 507 | 'sign': sign, 508 | 'pub_key': self.pubKey, 509 | 'payload': res['payload'] 510 | } 511 | res = self.request( 'submittx', 'POST', { 'params': json.dumps( params ) } ) 512 | 513 | # 成功したら TXID が返ってくる 514 | return res['result'] 515 | 516 | # 送金を行う 517 | def send( self, to, value, fee ): 518 | return self.newtx( to, value, fee, None ) 519 | 520 | # ブロックチェーンに任意データを書き込む 521 | def write( self, data, fee ): 522 | value = self.balance() - fee 523 | return self.newtx( self.address(), value, fee, data ) 524 | 525 | # ブロックチェーンから任意データを読み込む 526 | def read( self, txid ): 527 | # サーバからトランザクションデータを取得する 528 | try: 529 | res = self.request( 'transaction', 'GET', { 'txid': txid } ) 530 | except Exception as e: 531 | if e.args[0] == 404: 532 | return None 533 | else: 534 | raise 535 | 536 | # トランザクションデータから OP_RETURN 出力を探す 537 | data = None 538 | for tx in res: 539 | for vout in tx['vout']: 540 | if vout['scriptPubKey']['type'] == 'nulldata': 541 | data = vout['scriptPubKey']['asm'] 542 | 543 | # 見つからなかった場合 544 | if data is None: 545 | return None 546 | if data[:10] != 'OP_RETURN ': 547 | return None 548 | 549 | # 先頭の OP_RETURN を削除 550 | data = unhexlify( data[10:] ) 551 | return data 552 | --------------------------------------------------------------------------------