symbol-sdk@3.1.0でウォレット対応の暗号化メッセージを送る

# はじめに

以前、symbol-sdkのv3.0.7で、転送トランザクションを作成した際、暗号化メッセージがウォレットで復号できないことがありました。

https://qiita.com/planethouki/items/860d9f1095c087ccd98f

その記事のコメント欄に、 @Toshi_ma さんからこんなコメントを頂いておりました。

> sdk v2ではRawMessageやEncryptoMessageをなぜか二重に16進数にしていました

https://qiita.com/planethouki/items/860d9f1095c087ccd98f#comment-ac45f4c736c56a96e38a

そのため、メッセージ周りについては諦めていたのですが、このたび、symbol-sdkのアップデートでそのあたりができるようになったみたいです。

symbol-sdkのv3.0.11のリリースノートを見てみます。

> (Symbol-only) special handling for encrypted messages created by Symbol wallets

https://github.com/symbol/symbol/releases/tag/sdk%2Fjavascript%2Fv3.0.11

special handlingとは何なのかについて、コミットを追ってみてみます。

おそらくはこのコミットなのですが、`tryDecodeDeprecated` と `encodeDeprecated` が追加されているようです。

https://github.com/symbol/symbol/commit/ee9233026d7db19b88fbda1b3612260c113c8b22#diff-eecbdbedafc9c6640b58655b34a9525df99fe9bd5566b700e90c6d0614340d52

なので、この処理を使うことでウォレットでメッセージを読んだり、ウォレットで送ったメッセージが読めるようになったりするのではないかと思いました。

それでは試してみましょう。


# ウォレットでメッセージを読む

symbol-sdkで暗号化メッセージを作成し、トランザクションを送信します。そのトランザクションをウォレットで開いてみます。

TypeScriptのコードを示します。symbol-sdkはv3.1.0を使用しています。

```ts
import symbolSdk from 'symbol-sdk'
import axios from 'axios'
import { TextEncoder } from 'util';

const NODE_URL = process.env.NODE_URL
const PRIVATE_KEY = process.env.PRIVATE_KEY
const PUBLIC_KEY_RECEIVER = process.env.PUBLIC_KEY_RECEIVER

const network = symbolSdk.symbol.Network.TESTNET
const deadline = network.fromDatetime(new Date(Date.now() + 60000)).timestamp

const facade = new symbolSdk.facade.SymbolFacade(network.name)

const privateKey = new symbolSdk.PrivateKey(PRIVATE_KEY)
const keyPair = new symbolSdk.symbol.KeyPair(privateKey)

const currencyMosaicId = 0x72C0212E67A08BCEn
```

ここから暗号化メッセージの作成に入ります。`encodeDeprecated`を使用します。

```ts
const textEncoder = new TextEncoder();
const messageEncoder = new symbolSdk.symbol.MessageEncoder(keyPair);
const message = messageEncoder.encodeDeprecated(
    new symbolSdk.PublicKey(PUBLIC_KEY_RECEIVER),
    textEncoder.encode('sample message')
)
```

JSDocに`@deprecated`がついているためでしょうか、エディター上では取り消し線がつきました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172833/65dc2388-61d5-0b30-301a-009c53755503.png)

あとはトランザクションを作って送信します。

```ts
const transaction = facade.transactionFactory.create({
    type: 'transfer_transaction_v1',
    signerPublicKey: keyPair.publicKey.toString(),
    fee: 1000000n,
    deadline,
    recipientAddress: 'TARDV42KTAIZEF64EQT4NXT7K55DHWBEFIXVJQY',
    mosaics: [{ mosaicId: currencyMosaicId, amount: 1000000n }],
    message
})

const signature = facade.signTransaction(keyPair, transaction);
const jsonPayload = symbolSdk.symbol.SymbolTransactionFactory.attachSignature(transaction, signature)
const hash = facade.hashTransaction(transaction).toString();

await axios.put(`${NODE_URL}/transactions`, JSON.parse(jsonPayload));
```

さて、このトランザクションをウォレットで開いてみます。上記のコードで使用したアカウントをウォレットにもインポートしておきます。

メッセージ欄は、暗号化メッセージを示す`******`になっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172833/f2090b5a-3259-87de-7a20-0dc6b76b3228.png)

Decryptボタンを押して復号してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172833/0bb82bb6-085c-1bec-263e-6d574d81b08a.png)

無事復号することができました。

# ウォレットから送信したメッセージを読む

次は、ウォレットで暗号化メッセージを含んだ転送トランザクションを作成し、そのメッセージをsymbol-sdkで復号してみます。


まずはウォレットで暗号化メッセージを含んだトランザクションを作成し送信します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172833/5b2c8af1-bbea-b1c9-eee9-3ef5d3dd1a93.png)

次にTypeScriptのコードを示します。symbol-sdkはv3.1.0を使用しています。

```ts
import symbolSdk from 'symbol-sdk'
import axios from 'axios'

const NODE_URL = process.env.NODE_URL
const PRIVATE_KEY = process.env.PRIVATE_KEY
const PUBLIC_KEY_RECEIVER = process.env.PUBLIC_KEY_RECEIVER

const hash = 'D642F3D6394850362EC0F9C4B31F2C3F37391864C9EFC19AC987CBC405E560A5'

const transactionResponse = await axios.get(`${NODE_URL}/transactions/confirmed/${hash}`)
const encryptedMessage = transactionResponse.data.transaction.message

const privateKey = new symbolSdk.PrivateKey(PRIVATE_KEY)
const keyPair = new symbolSdk.symbol.KeyPair(privateKey)

const messageEncoder = new symbolSdk.symbol.MessageEncoder(keyPair);

const messageDecoded = messageEncoder.tryDecodeDeprecated(
    new symbolSdk.PublicKey(PUBLIC_KEY_RECEIVER),
    symbolSdk.utils.hexToUint8(encryptedMessage)
)

const textDecoder = new TextDecoder();
console.log(textDecoder.decode(messageDecoded.message));
```

`tryDecodeDeprecated`を使用しています。こちらも取り消し線が付きました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172833/651ef399-9bd4-52b7-83a5-9c833acdc1f9.png)

これを実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172833/27659e3b-d522-cc7a-eefc-af5f1f8a97cc.png)

ちゃんと復号できました。

# 平文メッセージは問題なし

おまけ。平文のメッセージのほうも見ていきましょう。

結論から言うと、symbol-sdkで送った平文メッセージは、`special handling`をすることなくウォレットで開くことができました。

以下のように、先頭に`0x00`を付けることが必要です。

```ts
const textEncoder = new TextEncoder();
const message = new Uint8Array([0x00, ...textEncoder.encode('あいうえお')]);
```

ウォレットで開くことができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172833/af0d3fd3-de33-ae7c-594b-fec8b19ca29b.png)

次に、ウォレットで送ったメッセージをsymbol-sdkで開いてみます。

ウォレットでは、このように送りました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172833/f1827924-0803-ecb3-6bf4-bd8508245659.png)

さて、まずは

```ts
const hash = "<your_transaction_hash>"
const transactionResponse = await axios.get(`${NODE_URL}/transactions/confirmed/${hash}`)
const plainMessageHex = transactionResponse.data.transaction.message
console.log(plainMessageHex)
```

このコードを実行すると、以下のようにでてきます。

```shell
00E3818BE3818DE3818FE38191E38193
```

先頭の1バイトの`0x00`は、平文メッセージを意味するヘッダーになります。「か」はUTF-8では`E3818B`であることからもわかります。

ですので、以下のように先頭バイトを取り除く必要があります。

```ts
const plainMessageUint8 = symbolSdk.utils.hexToUint8(plainMessageHex)
console.log(textDecoder.decode(plainMessageUint8.slice(1)))
```

これらを実行すると、

```shell
かきくけこ
```

と出力されました。

# おわりに

以前できなかったウォレットにおける暗号化メッセージのやりとりができるようになりました。

非推奨のマークが付けられていますが、それ専用のメソッドを使うことで実現していることがわかりました。

これで新しいsymbol-sdkでも、過去のメッセージを読むことができるようになりました。