PythonでSymbolブロックチェーンの送金プログラムを書いてみる。


本記事は「PythonでSymbolブロックチェーン」連載の1回目です。

PythonでSymbolブロックチェーンの送金プログラムを書いてみる。
Symbolブロックチェーンで一人が複数人に送金するトランザクションをPythonで作成する
Symbolブロックチェーンで複数人が同時に送金するトランザクションをPythonで作成する
Symbolブロックチェーンで複数人が同時に送金するトランザクションをPythonで作成する(2)

ブロックチェーンSymbol from nemのパブリックチェーンからはやくも一か月以上が経ちました。
Python版sdkがリリースされたということで、さっそく試してみます。

今回使用するライブラリはこちらです(Python3.7以上)。

まずインストールします。

pip install symbol-sdk-core-python

注) Windowsでインストールした人はエラーが発生することがあるようです。
以下のサイトを参考にBuild Tools for Visual Studio 2019の「C++ Build Tool」「MSCV v140」をインストールしてみてください。

KeyPair生成
from binascii import unhexlify
from symbolchain.core.CryptoTypes import PrivateKey
from symbolchain.core.symbol.KeyPair import KeyPair
b = unhexlify("896E43895B908AF5847ECCB2645543751D94BD87E71058B003417FED5123****")
prikey = PrivateKey(b)
keypair = KeyPair(prikey)

秘密鍵をバイナリ変換してPrivateKeyクラスを生成し、Keypairを作成します。

ModuleNotFoundError: No module named 'symbolchain'と表示された場合はインストールしたPythonと実行しているPythonのバージョンが異なります。python3などにインストールされている場合がありますので、実行環境を再確認してください。

ModuleNotFoundError: No module named 'symbolchain.core.symol'と表示された場合は古いバージョンのsymbol-sdkを参照しています。以後 symbolをsymと置き換えてお試しください。

PublicKey生成
pubkey = keypair.public_key
print(str(keypair.public_key))

keypairクラスから公開鍵を導出します。

Symbolのファサード生成
from symbolchain.core.facade.SymbolFacade import SymbolFacade
facade = SymbolFacade('testnet')

Symbolのパラメータがセットされたファサードクラスを生成します。NIS1版のファサードもあるようです。
testnet がテストネットのファサードです。メインネットはmainnetになります。

アドレス生成
address = facade.network.public_key_to_address(pubkey)
print(str(address))

#すでに知っているアドレスを使用する場合
address = SymbolFacade.Address("TD2JRXVHMX3BAOBOYURVASX4UUGABWK6YKSU***")

Symbolファサードを利用して公開鍵からアドレスを導出します。
なお、REST APIで取得できるアドレス情報はHEX文字列なので変換する必要があります。

from binascii import unhexlify
from symbolchain.core.symbol.Network import Address, Network

address = Address(unhexlify("688928C64395E16FAE78B30F970AE0249AD0B5B5B9DE1F5B"))
print(address) # NCESRRSDSXQW7LTYWMHZOCXAESNNBNNVXHPB6WY

from binascii import hexlify
address = Address("NCESRRSDSXQW7LTYWMHZOCXAESNNBNNVXHPB6WY")
print(hexlify(address.bytes))
秘密鍵の作成から始める

ここまで、秘密鍵を知っている前提で説明しましたが、秘密鍵を作成するところからの説明も補足しておきます。

from symbolchain.core.CryptoTypes import PrivateKey
from symbolchain.core.symbol.KeyPair import KeyPair
from symbolchain.core.facade.SymbolFacade import SymbolFacade

prikey = PrivateKey.random()
keypair = SymbolFacade.KeyPair(prikey)
pubkey = keypair.public_key
facade = SymbolFacade('testnet')
address = facade.network.public_key_to_address(pubkey)
print(str(address))
> TD2JRXVHMX3BAOBOYURVASX4UUGABWK6YKSU***

この出力されたアドレスを蛇口サイトに入力してテスト送信用のXYMを入手しておきます。
http://faucet.testnet.symboldev.network/
(テストネットで借りたXYMが余った場合はかならず元アドレスへ返しておきましょう)

トランザクションの有効期限を設定
import datetime
deadline = (int((datetime.datetime.today() + datetime.timedelta(hours=2)).timestamp()) - 1616694977) * 1000

1616694977 というのはSymbolが誕生したUTC秒になります。この時刻を基準にネットワーク内部で時刻が同期されています。2時間後を有効期限とします。メインネットは1615853185です。

添付するハッシュダイジェスト作成
import hashlib
digest = ""
with open('apostille.png','rb') as f:
   digest = hashlib.sha256(f.read()).hashdigest()

せっかくなのでトランザクションに「自分しか知らない、でも存在を証明したいファイル」のハッシュダイジェストを記録しましょう(このプロセスは不要です)。

トランザクション作成
tx = facade.transaction_factory.create({
  'type': 'transfer',
  'signer_public_key': pubkey,
  'fee': 100000,
  'deadline': deadline,
  'recipient_address': address,
  'mosaics': [(0x091F837E059AE13C, 1000000)],
  'message': bytes(1) + digest.encode('utf8')
})

0x091F837E059AE13C がテストネットでのXYMのモザイクID(トークン)になります。メインネットは0x6BED913FA20223F8です。
送金量は可分性が6(小数点以下6桁)なので 1000000倍した数値を指定します。今回は1XYMを送金します。
feeも同様にこれ以上の送金手数料は払えない、という額を指定します(今回は0.1XYM)。手数料の指定が小さいとトランザクションが混雑してきたときに承認されるための時間が長く必要になります。また、ノードごとに指定されている最小手数料よりも小さいとスパムとみなされ、トランザクションの状態を監視するステータスにも残りません。

署名
signature = facade.sign_transaction(keypair, tx)
tx.signature = signature.bytes

署名して、トランザクションに含めます。

JSON化
from binascii import hexlify
payload = {"payload": hexlify(tx.serialize()).decode('utf8').upper()}

import json
json = json.dumps(payload)

トランザクションをjson化します。

アナウンス
headers = {'Content-type': 'application/json'}
import http.client

conn = http.client.HTTPConnection("sym-test-01.opening-line.jp",3000)
conn.request("PUT", "/transactions", json,headers)

response = conn.getresponse()
print(response.status, response.reason)

テストネットにアナウンスして結果を受け取ります。
使用するAPIはこちらです。

確認
hash = facade.hash_transaction(tx)
print('https://sym-test-01.opening-line.jp:3001/transactionStatus/' + str(hash))

ハッシュ値を取得して状態を確認します。最初はUnconfirmedですが、30秒ほどするとConfirmが取れてSuccessになります。
Successになればエクスプローラーで確認してみましょう。

これでPythonを使ってSymbolブロックチェーンで送金することができました。
今回pythonで対話式に解説しましたが、実はJavaScriptを使ってブラウザのコンソールでも同じように送金を体験することができます。

ブロックチェーンSymbolにとって送金はまだほんの序の口です。ぜひいろいろな機能にチャレンジしてみてください。

最後に、全スクリプトを載せておきます。

from binascii import unhexlify
from symbolchain.core.CryptoTypes import PrivateKey
from symbolchain.core.symbol.KeyPair import KeyPair
from symbolchain.core.facade.SymbolFacade import SymbolFacade
import datetime
from binascii import hexlify
import json
import http.client

facade = SymbolFacade('testnet')

b = unhexlify("896E43895B908AF5847ECCB2645543751D94BD87E71058B003417FED5123****")
prikey = PrivateKey(b)
keypair = KeyPair(prikey)
pubkey = keypair.public_key
address = facade.network.public_key_to_address(pubkey)

deadline = (int((datetime.datetime.today() + datetime.timedelta(hours=2)).timestamp()) - 1616694977) * 1000
tx = facade.transaction_factory.create({
  'type': 'transfer',
  'signer_public_key': pubkey,
  'fee': 100000,
  'deadline': deadline,
  'recipient_address': address,
  'mosaics': [(0x091F837E059AE13C, 1000000)],
  'message': bytes(1) + "test".encode('utf8')
})

signature = facade.sign_transaction(keypair, tx)
tx.signature = signature.bytes

payload = {"payload": hexlify(tx.serialize()).decode('utf8').upper()}
jsonPayload = json.dumps(payload)
headers = {'Content-type': 'application/json'}
conn = http.client.HTTPConnection("sym-test-01.opening-line.jp",3000)
conn.request("PUT", "/transactions", jsonPayload,headers)

response = conn.getresponse()
print(response.status, response.reason)

hash = facade.hash_transaction(tx)
print('http://sym-test-01.opening-line.jp:3000/transactionStatus/' + str(hash))


以下はsymbol-sdk ver1.0のスクリプトです。

from binascii import unhexlify
from symbolchain.core.CryptoTypes import PrivateKey
from symbolchain.core.sym.KeyPair import KeyPair
from symbolchain.core.facade.SymFacade import SymFacade
import datetime
from binascii import hexlify
import json
import http.client

facade = SymFacade('public_test')

b = unhexlify("896E43895B908AF5847ECCB2645543751D94BD87E71058B003417FED5123****")
prikey = PrivateKey(b)
keypair = KeyPair(prikey)
pubkey = keypair.public_key
address = facade.network.public_key_to_address(pubkey)

deadline = (int((datetime.datetime.today() + datetime.timedelta(hours=2)).timestamp()) - 1616694977) * 1000
tx = facade.transaction_factory.create({
  'type': 'transfer',
  'signer_public_key': pubkey,
  'fee': 100000,
  'deadline': deadline,
  'recipient_address': address,
  'mosaics': [(0x091F837E059AE13C, 1000000)],
  'message': bytes(1) + "test".encode('utf8')
})

signature = facade.sign_transaction(keypair, tx)
tx.signature = signature.bytes

payload = {"payload": hexlify(tx.serialize()).decode('utf8').upper()}
jsonPayload = json.dumps(payload)
headers = {'Content-type': 'application/json'}
conn = http.client.HTTPConnection("sym-test-01.opening-line.jp",3000)
conn.request("PUT", "/transactions", jsonPayload,headers)

response = conn.getresponse()
print(response.status, response.reason)

hash = facade.hash_transaction(tx)
print('http://sym-test-01.opening-line.jp:3000/transactionStatus/' + str(hash))