Polkadot(Substrate)のアドレスとトランザクションについて

polkadot




この記事は株式会社Gincoのテックブログとして書いています。

今回は弊社プロダクトのGinco Enterprise Walletで取り扱っているPolkadotについて、ウォレットを作成する上で重要なアドレスとトランザクションの仕様について解説したいと思います。サンプルコードはGolangになります。

Polkadotの概要

Polkadotは、ブロックチェーン同士を相互運用可能な形で結びつけるための分散型システムです。これにより、異なるブロックチェーン間での情報の交換や資産の移動が容易になります。

異なるブロックチェーン同士を接続するためにPolkadotでは「パラチェーン」と呼ばれるサイドチェーンが存在します。Polkadot側がセキュリティを担保する仕組みのため、パラチェーンはセキュリティリスクを気にせず開発を行えるという利点があります。パラチェーンにはASTARやEfinity、Moonbeamなどがあります。

またPolkadotのコンセンサスアルゴリズムは、PoS(Proof of Stake)プロトコルを採用しており、エネルギー消費量が少なく、スケーラブルという特徴があります。

Substrate

Substrateは、開発者が容易に新しいブロックチェーンを作成し、他のブロックチェーンと結合することができるように開発されたフレームワークです。Polkadot自身もこのフレームワークによって構築されています。

特徴としてはハードフォークなしにプロトコルアップデートを行うことができるという点があります。

アドレスの仕様

SS58

アドレスフォーマットはSubstrate上で定義されているSS58という仕様に定義されています。

まずアドレスの長さについてですが、固定ではなく多くの種類があります。一番長い35のアドレスが一番使われていますのでこのアドレスについて解説します。

Address Length AddressType Raw account Checksum
3 1 1 1
4 1 2 1
5 1 2 2
6 1 4 1
7 1 4 2
8 1 4 3
9 1 4 4
10 1 8 1
11 1 8 2
12 1 8 3
13 1 8 4
14 1 8 5
15 1 8 6
16 1 8 7
17 1 8 8
35 1 32 2

SS58のフォーマットはBitcoinで使われているBase58Check形式に基づいていますが、Substrate ベースのチェーンに合わせていくつかの変更が加えられています。

アドレスの生成方法は下記になります。

base58encode ( concat ( <address-type>, <address>, <checksum> ) )

<address-type>はチェーン毎に区別をつけるための値となります。例えば、Polkadotは0x0(0)、テストネットの場合は0x2a(42)となります。

<address>はドキュメント上でaddressと記載されていますが公開鍵が入ります。

<checksum>は、”SS58PRE”という文字列をprefixとして、<address-type>と<address>を結合したものをプリイメージとしてblake2b-512でハッシュした先頭2バイトがchecksumとなります。

concat ("SS58PRE", <address-type>, <address> )

上記に沿ったGolangでの実装例が下記になります。

func EncodeAddress(pubKey []byte, isTestnet bool) string {
var raw []byte
addressType := []byte{0x00}
if isTestnet {
// Westend is 42
addressType = []byte{0x2A}
}
raw = append(addressType, pubKey...)
checksum := blake2b.Sum512(append(prefix, raw...))
return base58.NewBase58(base58.BITCOIN).Encode(append(raw, checksum[0:2]...))
}

トランザクションの構築手順

トランザクションの構築手順は下記の通りになります。

  1. 未署名トランザクションを構築し、SCALEエンコードしてペイロードを生成
  2. ペイロードに署名をし、署名済トランザクションを構築
  3. 署名済トランザクションをSCALEエンコードし、rawtxを作成しネットワークに送金
  4. 事前にトランザクションハッシュを求める場合はrawtxをハッシュして生成

未署名トランザクションの構築

ペイロードを作成するための未署名トランザクションのフォーマットは下記になります。

type UnsignedTransaction struct {
Method Method `scale:"-"`
EraPeriod uint
Nonce uint
Tip uint
SpecVersion uint32
TransactionVersion uint32
GenesisHash [32]byte
BlockHash [32]byte
}

Methodはトランザクションで実行する内容を決めるもので送金の場合はBalances PalletのTransferを使用します。

EraPeriodはトランザクションの有効期限を決める項目でオプショナルです。NonceはEthereumと同じで処理される順番を設定します。

Tipは手数料とは別に優先的に取り込まれるように設定するものですが、現状のPolkadotは混雑していないので設定の必要はありません。

SpecVersionにはRunTimeのバージョンを、TransactionVersionはRunTimeで定義されたトランザクションのバージョンを     設定する項目です。GenesisHashは名称通りの値で、BlockHashは有効期限(EraPeriod)を設定する場合はトランザクション作成時点のブロックのハッシュを設定します。

未署名トランザクションの構築方法は下記になります。

func NewUnsignedTransaction(method Method, eraPeriod, nonce uint, specVersion, transactionVersion uint32, genesisHash, currentBlockHash string) (UnsignedTransaction, error) {
var (
gHash [32]byte
cHash [32]byte
)
g, err := hex.DecodeString(strings.TrimPrefix(genesisHash, "0x"))
if err != nil {
return UnsignedTransaction{}, err
}
copy(gHash[:], g)
if eraPeriod == 0 {
cHash = gHash
} else {
c, err := hex.DecodeString(strings.TrimPrefix(currentBlockHash, "0x"))
if err != nil {
return UnsignedTransaction{}, err
}
copy(cHash[:], c)
}
return UnsignedTransaction{
Method: method,
EraPeriod: eraPeriod,
Nonce: nonce,
Tip: 0,
SpecVersion: specVersion,
TransactionVersion: transactionVersion,
GenesisHash: gHash,
BlockHash: cHash,
}, nil
}

ペイロードの生成

ペイロードの生成方法方は未署名トランザクションをSCALEエンコードするだけになりますが、エンコード結果が256バイトよりも大きい場合はblake2b256ハッシュでペイロード短くしてあげる必要があります。

Transfer Methodでは256バイトを超えませんが、マルチシグ関連のトランザクションの場合は超える場合があるため注意が必要です。

func GenerateSigningPayload(method Method, eraPeriod, nonce uint, specVersion, transactionVersion uint32, genesisHash, currentBlockHash string) (string, error) {
unsignedTx, err := NewUnsignedTransaction(method, eraPeriod, nonce, specVersion, transactionVersion, genesisHash, currentBlockHash)
if err != nil {
return "", err
}
e, err := unsignedTx.Encode()
if err != nil {
return "", err
}
if len(e) > 256 {
h := blake2b.Sum256(e)
e = h[:]
}
return hex.EncodeToString(e), nil
}

署名アルゴリズム

Substrateは3種類のスキームを採用しています。

ECDSA

BitcoinとEthereumと同じsecp256k1曲線を使用した署名スキームです。

Ed25519

Ed25519 は、 Curve25519を使用した EdDSA 署名スキームです。セキュリティを損なうことなく非常に高速に動作するように、設計と実装のいくつかのレベルで慎重に設計されています。

SR25519

SR25519 はEd25519と同じ楕円曲線に基づいています。ただし、EdDSA スキームの代わりに Schnorr 署名を使用します。

実際に署名した後は署名結果に対して、署名スキーマ別に決まった値をprefixにつける必要があります。

ed25519は0x00を、sr25519は0x01を、ecdsaは0x02をprefixとして追加します。

func Sign(key []byte, payload[]byte) string {
signature := sign(key, payload)
// signature = scheme + signature. ed25519: 0x00, sr25519: 0x01, ecdsa: 0x02
return fmt.Sprintf("0x00%x", signature)
}

署名済みトランザクションのフォーマット

rawtxを作成するための署名済みトランザクションのフォーマットは下記になります。

type SignedTransaction struct {
SignedFlag *big.Int // signing bit set
FromMultiAddress uint8
FromPubKey [32]byte
Signature [65]byte // signType(1 byte) + signature(64 byte) ed25519:0, sr25519:1
EraPeriod uint
Nonce uint
Tip uint
Method Method scale:"-"
}

SignedFlagはV4の場合33を指定します。FromMultiAddressのMultiAddressは正確にはmulti-format addressの意味ですが今回は使用しないため0を入れます。FromPubKeyは送金元の公開鍵です。他は未署名トランザクションで使用した値を設定します。

構築した署名済みトランザクションのエンコード方法は下記になります。気をつける点としては署名済みトランザクションのエンコード結果のlengthをrawtxの先頭に設定する必要があります。

func (s *SignedTransaction) Encode() ([]byte, error) {
e, err := scale.Marshal(s)
if err != nil {
return nil, err
}
m, err := s.Method.Encode()
if err != nil {
return nil, err
}
e = append(e, m...)
length := len(e)

l, err := scale.Marshal(big.NewInt(int64(length)))
if err != nil {
return nil, err
}
return append(l, e...), nil
}

まとめ

この記事ではPolakdot(Substrate)でのアドレスとトランザクションの生成方法についてまとめました。

ウォレットを作るにはアドレスやトランザクションの詳細な仕様について完璧に把握しないと作れないためかなりドキュメントを読み込みます。しかし、ドキュメントはウォレットを作ることを想定していないため仕様の詳細が記載されていない場合が多く、実際のソースコードを読んで理解しないといけないなど結構ハードルが高い一面もあります。次回はマルチシグのプロトコルについて解説したいと思います。

株式会社Gincoではブロックチェーンを学びたい方、ウォレットについて詳しくなりたい方を募集していますので下記リンクから是非ご応募ください。

株式会社Ginco の求人一覧

The following two tabs change content below.

髙妻智一

2013年CyberAgent新卒入社 スマホゲームを作る子会社に所属し、サーバーサイドのエンジニアを担当。2年目の終わりから新規子会社の立ち上げに参加し、サーバーサイドのエンジニアリーダーとしてサービースのリリースから運用までを担当。 2018年仮想通貨のスマホウォレットを提供するGinco Incにブロックチェーンエンジニアとして入社。






よく読まれている関連記事はこちら




コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です