細かいことはドキュメントに書いてなかったので、コードを読んでまとめました。
まとめ
- ビットコインのトランザクションの中にデータを入れている
- 過去、色々なデータフォーマットがあった
- エンコーディングの種類と、それぞれの種類でデータのバージョンがあった
- 現在のデフォルトは、1-of-3 Multi-sig
- Multi-sigの場合、2つのニセ公開鍵の中にデータを入れている
前提知識
- 技術的な基礎知識
- ビットコインの技術的知識
- アドレスがどのように作られているのか? (pubkey, pukeyhash, UTXO, uxidなど)
- Scriptというスクリプト言語を読める
- Multi-sig の知識
- Multi-sig そのScriptの形(OPコード)
- Counterpartyの基礎知識
- bitcoind, counterpartydがどう動いているのか、など
3つのエンコーディングの種類
- OP_RETURN として返す場合、opreturnをエンコーディング・パラメータとして指定する
- bitcoind 0.9.0以降40byte制限があるので、全てのCounterpartyトランザクションが可能なわけではない
- multisigトランザクションとして返す場合、multisig をエンコーディング・パラメータとして指定する
- auto は、今のところ multisig を指定しているのと同じ
- pubkeyhash の場合、 pubkey を指定する
- UTXO setを汚染するので、推奨しない
ARC4
送信者の一つめのtxid(公開鍵ではない?)をパスワードとして、2つ目3つ目を暗号化する
データの保存方法
- エンコード: UTF-8
- プリフィックス: CNTRPRTY
- 3種類のアウトプットがある
- データは、ARC4で暗号化
- 1-of-3 multi-sig
- 3つの内の最初の2が、ニセ公開鍵(データ)
- 残りの1つが、元の公開鍵
- 0でパディング
Multi-sig の Script
こんな感じで構築
tx_script = OP_1 # OP_1
tx_script += op_push(33) # Push bytes of data chunk (fake) public key (1/2)
tx_script += data_pubkey_1 # (Fake) public key (1/2)
tx_script += op_push(33) # Push bytes of data chunk (fake) public key (2/2)
tx_script += data_pubkey_2 # (Fake) public key (2/2)
tx_script += op_push(len(dust_return_pubkey)) # Push bytes of source public key
tx_script += dust_return_pubkey # Source public key
tx_script += OP_3 # OP_3
tx_script += OP_CHECKMULTISIG # OP_CHECKMULTISIG
データ・フォーマット
# Counterparty protocol
TXTYPE_FORMAT = '>I'
Python3 の struct.pack, struct.unpackを使っている
Python 上で文字列データとして表される C の構造体データの変換をする
issuranceの場合
FORMAT_1 = '>QQ?'
LENGTH_1 = 8 + 8 + 1
FORMAT_2 = '>QQ??If'
LENGTH_2 = 8 + 8 + 1 + 1 + 4 + 4
データ・フォーマットが何回か変更された模様
FORMAT_1の場合
- Asset ID (整数)
- Quantity (整数)
- Divisible (bool)
if len(message) != LENGTH_1:
raise exceptions.UnpackError
asset_id, quantity, divisible = struct.unpack(FORMAT_1, message)
callable_, call_date, call_price, description = False, 0, 0.0, ''
FORMAT_2の場合
- Asset ID
- Quantity
- Divisible
- callable_
- call_date
- call_price
- Description
asset_id, quantity, divisible, callable_, call_date, call_price, description = struct.unpack(curr_format, message)
call_price = round(call_price, 6) # TODO: arbitrary
Counterpartyで使われるフォーマット
- “>” は、ビッグエンディアンの意味
- フォーマット文字列の前に整数がついているのは、繰り返し
フォーマット | C での型 | Python 型 | 標準のサイズ | 備考 |
---|---|---|---|---|
Q | unsigned long long | 整数型 | 8 | (2), (3) |
? | _Bool | 真偽値型(bool) | 1 | (1) |
I | unsigned int | 整数型 | 4 | (3) |
f | float | 浮動小数点型 | 4 | (4) |
H | unsigned short | 整数型 | 2 | (3) |
s | char[] | 文字列 |
フォーマットのコード内の例
https://github.com/CounterpartyXCP/counterpartyd/blob/master/lib/messages/issuance.py#L13
トランザクションのパース
def parse_tx(db, tx): の中で mesage_type_idを取得して、そこからデータの形を決めて、パース
if len(tx['data']) > 4:
try:
message_type_id = struct.unpack(config.TXTYPE_FORMAT, tx['data'][:4])[0]
except struct.error: # Deterministically raised.
message_type_id = None
else:
message_type_id = None
上記の続き。
message_type_idを見て、メッセージの種類によって個別にパース
# Protocol change.
rps_enabled = tx['block_index'] >= 308500 or config.TESTNET
message = tx['data'][4:]
if message_type_id == send.ID:
send.parse(db, tx, message)
elif message_type_id == order.ID:
order.parse(db, tx, message)
elif message_type_id == btcpay.ID:
btcpay.parse(db, tx, message)
elif message_type_id == issuance.ID:
issuance.parse(db, tx, message)
elif message_type_id == broadcast.ID:
broadcast.parse(db, tx, message)
elif message_type_id == bet.ID:
bet.parse(db, tx, message)
elif message_type_id == dividend.ID:
dividend.parse(db, tx, message)
elif message_type_id == cancel.ID:
cancel.parse(db, tx, message)
elif message_type_id == rps.ID and rps_enabled:
rps.parse(db, tx, message)
elif message_type_id == rpsresolve.ID and rps_enabled:
rpsresolve.parse(db, tx, message)
elif message_type_id == publish.ID and tx['block_index'] != config.MEMPOOL_BLOCK_INDEX:
publish.parse(db, tx, message)
elif message_type_id == execute.ID and tx['block_index'] != config.MEMPOOL_BLOCK_INDEX:
execute.parse(db, tx, message)
elif message_type_id == destroy.ID:
destroy.parse(db, tx, message)
else:
API
- 全ての create_ API は、署名されてないhexエンコードされた生トランザクション・stringを返す
メッセージ内コンポーネント
- 送信元アドレス
- 送信先アドレス(optional)
- 送信元から送信先に送られるビットコインの量 (もし存在するなら)
- 送信料金。ビットコイン。ブロックにトランザクションを入れるビットコインマイナーに支払われる。
- ‘データ’。 特別に作られたトランザクション・アウトプットに組み込まれている。
Assetのデータ
Protocol Specification | Counterparty
- Asset name
- Asset ID
- Description
- Divisiblity
- Callability
- Call date (if callable)
may be delayed with later issuances - Call price (if callable) (non‐negative) may be increased with later issuances
メッセージの種類
- Send
- Order
- BTCPay
- Issue
- Broadcast
- Bet
- Dividend
- Burn
- Cancel
どのようにデータは保存されているか?
以下のフォーラムによると
Detect Counterparty transaction – Development & Technical Discussions on Counterparty Forums
- UTF-8 の CNTRPRTY の prefixで始まる
- データは、3種類のアウトプットに保存される
- 全てのデータは、最初の送信者の公開鍵をパスワードにしたARC4の暗号化で難しくしている
- Multisig の 1-of-3 の最初の公開鍵は送信者のもの
- 残り2つの公開鍵は、エンコードされたデータ(0でパディング、length byte でプリフィックス)
コードを見る限り、上記一部間違っているようです。
Counterparty勉強会
プレゼン資料
英語です。以下の動画で日本語で説明しています。
http://crypto-tech.jp/counterparty-doc/#/
YouTube 動画
参考記事
CounterpartyのNumeric Assetを作成する – @yzono blog
検索用語
- ビットコイン、カウンターパーティ
- Bitcoin, Bitcoin 2.0, Counterparty, Counterwallet