Symbolブロックチェーンのメタデータを登録するトランザクションをPythonで作成する

Symbolが提供するアグリゲートトランザクションは、送金だけではなく、ブロックチェーンの状態を変更させるトランザクションを混ぜて作成させることが可能です。

つまり、入金と同時にマルチシグの構成を変更して所有権を譲渡したり、
入金と同時に、メタデータに卒業証明を記入したり
アポスティーユと同時に、数量が1つだけの代替不可トークンを発行したりすることができます。

今回はメタデータの作成方法について説明してみます。

### 事前準備

```python
import sha3
import json
import http.client
import datetime
from binascii import hexlify
from binascii import unhexlify
from symbolchain.core.CryptoTypes import PrivateKey
from symbolchain.core.sym.KeyPair import KeyPair
from symbolchain.core.facade.SymFacade import SymFacade
from symbolchain.core.CryptoTypes import PublicKey
from symbolchain.core.sym.IdGenerator import generate_namespace_id
from symbolchain.core.sym.MerkleHashBuilder import MerkleHashBuilder
from symbolchain.core.CryptoTypes import Hash256
```

前回までに使用したパッケージを事前にimportしておきます。

### メタデータの作成

```python
hasher = sha3.sha3_256()
hasher.update('certificate'.encode('utf8'))
digest = hasher.digest()
metadataKey = int.from_bytes(digest[0:8], 'little')
metadataValue = 'aaa'
```

今回はkey:certificate value:aaa として登録します。
key値の作成方法はいろいろあるかとは思いますが、NodeJS版SDKも上記方法で統一されているようです。

### メタデータの登録
```python
facade = SymFacade('public_test')

b = unhexlify("896E43895B908AF5847ECCB2645543751D94BD87E71058B003417FED5123****")

alicePrikey = PrivateKey(b)
aliceKeypair = KeyPair(alicePrikey)
alicePubkey = aliceKeypair.public_key
aliceAddress = facade.network.public_key_to_address(alicePubkey)
str(aliceAddress)


bobPrikey = PrivateKey.random()
bobKeypair = SymFacade.KeyPair(bobPrikey)
print(str(facade.network.public_key_to_address(bobKeypair.public_key)))
print(str(bobKeypair.public_key))
strBobPubkey = str(bobKeypair.public_key)
strBobAddress = str(facade.network.public_key_to_address(bobKeypair.public_key))

bobPubkey = PublicKey(unhexlify(strBobPubkey))
bobAddress = SymFacade.Address(strBobAddress)

aliceTx = facade.transaction_factory.create_embedded({
    'type': 'accountMetadata',
    'signer_public_key': alicePubkey,
    'target_address': bobAddress,
    'scoped_metadata_key': metadataKey,
    'value_size_delta': len(metadataValue),
    'value': metadataValue
})

hash_builder = MerkleHashBuilder()
hash_builder.update(Hash256(sha3.sha3_256(aliceTx.serialize()).digest()))
merkle_hash = hash_builder.final()

deadline = (int((datetime.datetime.today() + datetime.timedelta(hours=2)).timestamp()) - 1616694977) * 1000

aggregate = facade.transaction_factory.create({
    'type': 'aggregateComplete',
    'signer_public_key': alicePubkey,
    'fee': 1000000,
    'deadline': deadline,
    'transactions_hash': merkle_hash,
    'transactions': [aliceTx]
})

signature = facade.sign_transaction(aliceKeypair, aggregate)
aggregate.signature = signature.bytes
hash = facade.hash_transaction(aggregate).bytes
hexlifiedHash = hexlify(hash)
print(hexlifiedHash)

#bob側で署名して
hexlifiedSignedHash = str(bobKeypair.sign(unhexlify(hexlifiedHash)))
cosignature = (0, bobPubkey.bytes, unhexlify(hexlifiedSignedHash))
aggregate.cosignatures.append(cosignature)

payload = {"payload": hexlify(aggregate.serialize()).decode('utf8').upper()}

strJson = json.dumps(payload)
headers = {'Content-type': 'application/json'}

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

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

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

AliceからBobにメタデータを記録します。
アグリゲートコンプリートによるオフライン署名の方法で実施しました。

https://qiita.com/nem_takanobu/items/99557ff2299354aabcc4


トランザクションの組み立て以外は、送金トランザクションの時と全く同じですね。


Bobのメタデータに記録するので、Aliceの署名だけではなく、Bobのオプトイン署名も必要になります。Symbolではトークンを送りつける以外のあらゆる他アカウントへの状態操作について、受け取るアカウントのオプトイン署名が必要になります。オプトインを要せず相手に譲渡したい場合はNFTなどトークンの形に置き換えて送信する方法が考えられます。

### メタデータ更新

次に登録したメタデータを修正してみます。

##### 更新内容の定義
```python
newMetadataValue = 'bbb'
xorValue = "".join([chr(ord(data) ^ ord(code))
    for (data, code) in zip(metadataValue, newMetadataValue)]).encode().hex()
```
value値 aaaをbbbに変更します。
ただ、トランザクションにはbbbではなく、XORの差分値を登録します。
ここがトレース可能なブロックチェーンならではの部分ですね。



```python
aliceTx = facade.transaction_factory.create_embedded({
    'type': 'accountMetadata',
    'signer_public_key': alicePubkey,
    'target_address': bobAddress,
    'scoped_metadata_key': metadataKey,
    'value_size_delta': 0,
    'value': unhexlify(xorValue)
})

hash_builder = MerkleHashBuilder()
hash_builder.update(Hash256(sha3.sha3_256(aliceTx.serialize()).digest()))
merkle_hash = hash_builder.final()

deadline = (int((datetime.datetime.today() + datetime.timedelta(hours=2)).timestamp()) - 1616694977) * 1000

aggregate = facade.transaction_factory.create({
    'type': 'aggregateComplete',
    'signer_public_key': alicePubkey,
    'fee': 1000000,
    'deadline': deadline,
    'transactions_hash': merkle_hash,
    'transactions': [aliceTx]
})

signature = facade.sign_transaction(aliceKeypair, aggregate)
aggregate.signature = signature.bytes

hash = facade.hash_transaction(aggregate).bytes
hexlifiedHash = hexlify(hash)
print(hexlifiedHash)

#bob側で署名して
hexlifiedSignedHash = str(bobKeypair.sign(unhexlify(hexlifiedHash)))
cosignature = (0, bobPubkey.bytes, unhexlify(hexlifiedSignedHash))
aggregate.cosignatures.append(cosignature)

payload = {"payload": hexlify(aggregate.serialize()).decode('utf8').upper()}

strJson = json.dumps(payload)

headers = {'Content-type': 'application/json'}

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

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

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

value_size_delta は現在バグのため負値を指定することができません。
ぜひお試しください。