symbol-sdk@3.0.7で作るシークレットロック／プルーフトランザクション

# はじめに

こんにちは。

symbol-sdkの3系、トランザクションシリーズ第七弾です。

今回はシークレットロック・シークレットプルーフをやっていきます。

https://www.npmjs.com/package/symbol-sdk/v/3.0.7

## 注意事項

書いてあるコードについて、正確性や完全性を保証するものではありません。あくまで参考程度として頂き、最新情報は公式ドキュメンテーションをご確認ください。

# デッドラインと鍵ペア

まずはデッドラインと鍵ペアです。

```jsx
import symbolSdk from 'symbol-sdk';

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

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

const privateKey = new symbolSdk.PrivateKey(PRIVATE_KEY);
const keyPair = new facade.constructor.KeyPair(privateKey);
```

# TextEncoderとハッシュ関数

文字列をUint8Arrayに変換するので、 `TextEncoder` をインポートします。

```jsx
import { TextEncoder } from 'util';

const textEncoder = new TextEncoder();
```

ハッシュ関数ライブラリをインポートします。 `symbol-sdk` の依存ライブラリでもあるので、追加でインストールしなくても使えます。

```jsx
import { sha3_256 } from '@noble/hashes/sha3';
import { sha256 } from '@noble/hashes/sha256';
import { ripemd160 } from '@noble/hashes/ripemd160';
```

# プルーフ

プルーフを先に作ります。文字列です。

```jsx
const proof = `secret${Date.now()}`;
```

# ハッシュアルゴリズム

使用するアルゴリズムに合わせて、 `LockHashAlgorithm` を選択します。

```jsx
const hashAlgorithm = symbolSdk.symbol.LockHashAlgorithm.SHA3_256;
const hashAlgorithm = symbolSdk.symbol.LockHashAlgorithm.HASH_160;
const hashAlgorithm = symbolSdk.symbol.LockHashAlgorithm.HASH_256;
```

# シークレット

各種アルゴリズムに合わせてシークレットを作成します。

## SHA3_256

`SHA3_256` ではこのような形になります。この `secretBuffer` は `Uint8Array` 型となります。

```jsx
const secretBuffer = sha3_256.create().update(textEncoder.encode(proof)).digest();
```

トランザクションに取り込むときに様々な型が使用できます。

やってみた感じだと、`Uint8Array` 型そのままか、16進数文字列が使用できました。

```jsx
const secret = secretBuffer;
const secret = symbolSdk.utils.uint8ToHex(secret);
```

## HASH_160

`HASH_160` ですと、SHA256でハッシュした後、RIPEMD160でハッシュします。ハッシュの出力が20バイトになるので、0で埋めて32バイトにしなければなりません。

16進数文字列で扱うのが慣れているので、文字列で0埋めしました。

```jsx
const secret1 = sha256.create().update(textEncoder.encode(proof)).digest();
const secret2 = ripemd160.create().update(secret1).digest();
const secret = symbolSdk.utils.uint8ToHex(secret2).padEnd(64, "0");
```

## HASH_256

`HASH_256` ですと、SHA256で２回ハッシュします。

```jsx
const secret1 = sha256.create().update(textEncoder.encode(proof)).digest();
const secretBuffer = sha256.create().update(secret1).digest();
```

トランザクションに取り込むときに様々な型が使用できます。

```jsx
const secret = secretBuffer;
const secret = new Uint8Array(secretBuffer);
const secret = symbolSdk.utils.uint8ToHex(secretBuffer);
```

# ロック期間

ブロック数でシークレットロックの有効期限を指定します。BigInt型か `BlockDuration` 型が使えます。

```jsx
const duration = 86400n;
const duration = new symbolSdk.symbol.BlockDuration(86400n);
```

# シークレットロックトランザクション

それではシークレットロックトランザクションを作ります。

上記で作成したパラメータと共に、 `recipientAddress` と `mosaic` を指定します。

```jsx
const transaction = facade.transactionFactory.create({
  type: 'secret_lock_transaction_v1',
  signerPublicKey: keyPair.publicKey.toString(),
  fee: 1000000n,
  deadline,
  recipientAddress: ADDRESS_RECEIVER_1,
  secret,
  mosaic: { mosaicId: 0xE74B99BA41F4AFEEn, amount: 1000000n },
  duration,
  hashAlgorithm,
});
```

## 署名と送信

署名して送信します。

```jsx
const signature = facade.signTransaction(keyPair, transaction);
const jsonPayload = facade.transactionFactory.constructor.attachSignature(transaction, signature);
const hash = facade.hashTransaction(transaction).toString();
console.log(hash);

const sendRes = await axios.put(`${NODE_URL}/transactions`, jsonPayload).then((res) => res.data);
console.log(sendRes);
```

## 承認されるのを待つ

この次にシークレットプルーフトランザクションを送信したいので、ここでシークレットロックトランザクションの承認を待ちます。

```jsx
while (true) {
  await new Promise((resolve) => setTimeout(resolve, 5000));

  const statusRes = await axios.get(`${NODE_URL}/transactionStatus/${hash}`).then((res) => res.data);
  console.log(statusRes);

  if (statusRes.group === 'confirmed') {
    break;
  }

  if (statusRes.group !== 'unconfirmed') {
    throw new Error();
  }
}
```

# シークレットプルーフトランザクション

シークレットロックトランザクションが承認されたので、シークレットプルーフトランザクションを作成していきます。

```jsx
const transaction = facade.transactionFactory.create({
  type: 'secret_proof_transaction_v1',
  signerPublicKey: keyPair.publicKey.toString(),
  fee: 1000000n,
  deadline,
  recipientAddress: ADDRESS_RECEIVER_1,
  secret: secret,
  hashAlgorithm,
  proof: proof,
});
```

## 署名と送信とステータス確認

署名して送信してステータスを確認します。

```jsx
const signature = facade.signTransaction(keyPair, transaction);
const jsonPayload = facade.transactionFactory.constructor.attachSignature(transaction, signature);
const hash = facade.hashTransaction(transaction).toString();

const sendRes = await node.put("/transactions", jsonPayload).then((res) => res.data);
console.log(sendRes);

await new Promise((resolve) => setTimeout(resolve, 1000));

const statusRes = await node.get("/transactionStatus/" + hash).then((res) => res.data);
console.log(statusRes);
```

# おわりに

今回は、シークレットロックトランザクションとシークレットプルーフトランザクションをやっていきました。記事の都合上、２つのトランザクションをいっぺんに作りましたが、アプリへの実装の際には別々に使うことがほとんどだと思います。

# シリーズ

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

https://qiita.com/planethouki/items/25f104ef4930b019ddfb

https://qiita.com/planethouki/items/525b5a582f78be57217b

https://qiita.com/planethouki/items/c0336b479f6c11918183

https://qiita.com/planethouki/items/348bbfdc550ab5528c31

https://qiita.com/planethouki/items/b7f0ae2c934a90797ebb