2020/09/09

[ADA]cardano-serialization-libをPythonから呼べるようにしました

cardanorustpython

概要

ADAの決済やdepositのシステムを構築するのに、HDWalletを自作する必要が出てきたので、対応しました。(現在も改良中です)

Emurgo/cardano-serialization-libはcardanoのwalletの実装に使えるjavascript向けのライブラリで、
コア部分がrustで実装されています。

Expressなどのjavascriptベースのシステムであれば、全然こちらを利用するのが良いのですが・・
私は、backendのサーバーはpythonを普段利用しているので、このライブラリを改良してpythonで利用できるようにしようと思い、改造しました。

本家をforkして、kumanote/cardano-serialization-libに作成しています。

以下のような関数を作ろうかなと考えています。

  • mnemonicからaddressを生成する(いったんEnterpriseのAddressだけで良いかなと思っています)
  • 生成したaddressにあるUTXOを使って、Transactionを作成して署名する

実装

基本的には、本家のソースコードの踏襲なので、rust側のコードで主に変更があるのは、以下です。

  • Javascript向けのコードは無くし、
  • Pythonから呼べるようにする

本記事では、PythonからRustのコードを呼ぶ部分を記載しておきます。

Pyo3の組み込み

Cargo.toml

  • crate-typeにcdylibを指定しておきます
  • pyo3を依存関係に追加します
[lib]
name = "cardano_serialization_lib_py"
crate-type = ["cdylib", "rlib"]

[dependencies.pyo3]
version = "0.11.1"
features = ["extension-module"]

lib.rs

  • pymoduleを定義します。(python側はこの名前でimportできるようになります)
  • pyfnを定義します
    • PyResultを返却することに注意
#[pymodule]
fn cardano_serialization_lib(py: Python, m: &PyModule) -> PyResult<()> {
    #[pyfn(m, "generate_bip32_enterprise_address")]
    pub fn generate_bip32_enterprise_address_py(
        _py: Python, phrase: String, password: String, network: u8, account: u32, chains: u32, index: u32
    ) -> PyResult<String> {
        let out = generate_bip32_enterprise_address(phrase, password, network, account, chains, index);
        Ok(out)
    }
    Ok(())
}

fn generate_bip32_enterprise_address(phrase: String, password: String, network: u8, account: u32, chains: u32, index: u32) -> String {
    let mnemonic = Mnemonic::from_phrase(phrase.as_str(), Language::English).unwrap();
    let entropy = mnemonic.entropy();
    let root_key = Bip32PrivateKey::from_bip39_entropy(&entropy, password.as_bytes());
    let spend = root_key
        .derive(harden(1852))
        .derive(harden(1815))
        .derive(harden(account))
        .derive(chains)
        .derive(index)
        .to_public();
    let spend_raw_key: PublicKey = spend.into();
    let spend_cred = StakeCredential::from_keyhash(&spend_raw_key.hash());
    let address = EnterpriseAddress::new(network, &spend_cred).to_address();
    address.to_bech32(None)
}

Build

私はMacで開発しているので、以下のコマンドでビルドしました

% cargo rustc --release -- -C link-arg=-undefined -C link-arg=dynamic_lookup

Deploy

ビルドしてできた dylib ファイルにシンボリックリンクを作成しておきます。
この時に pymodule でつけた名前と同じファイル名.soという名前でシンボリックリンクを作成する必要があります。
(シンボリックリンクではなく、単純にファイルをコピーしてリネームしてもOKです。)

$ ln -s `pwd`/rust/target/release/libcardano_serialization_lib_py.dylib ./cardano_serialization_lib.so

Run

シンボリックリンクを作成した同一ディレクトリで python を実行します。

import cardano_serialization_lib

phrase = "art forum devote street sure rather head chuckle guard poverty release quote oak craft enemy"
password = ""
network = 0
account = 0
chains = 0
index = 0
address = cardano_serialization_lib.generate_bip32_enterprise_address(phrase, password, network, account, chains, index)
print(address)
addr_test1vpu5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5eg57c2qv

以上です。