13
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

代理アカウントを立ててアカウントを守る - Symbol Blockchain

Last updated at Posted at 2024-08-24

環境

  • symbol-sdk: 3.2.2

登場人物

  • Alice …Carol のファン
  • Bob …Carol のマネージャ
  • Carol …アイドル

Carol のファンである Alice は、Carol にプレゼントを贈りたい。
マネージャの Bob はプレゼントに偽装した危険物を Carol が受け取ってしまうとのを防ぎたい。
ファンへの返事は Carol 本人からして欲しい。

アカウント

Alice

  • 一般的な普通のアカウント

Bob

  • Carol アカウントの親シグ

Carol

  • Bob アカウントの子シグ
  • Bob 以外から受信できない
  • 送信は誰にでもできる

Carol への制限追加

以下をアグリゲートでまとめて送信します。

  • Bob 以外からの受信不可
  • マルチシグ化(連署者は Bob)
ソース
import {
  descriptors,
  KeyPair,
  models,
  Network,
  SymbolAccount,
  SymbolFacade,
  SymbolTransactionFactory,
} from "symbol-sdk/symbol";
import { Env } from "./Env.js";
import { PrivateKey, utils } from "symbol-sdk";

const facade = new SymbolFacade(Network.TESTNET);
const bobPrikey = new PrivateKey(Env.BOB_PRIVATE_KEY);
const bob = new SymbolAccount(facade, new KeyPair(bobPrikey));
const carolPrikey = new PrivateKey(Env.CAROL_PRIVATE_KEY);
const carol = new SymbolAccount(facade, new KeyPair(carolPrikey));

// ボブ以外から受信不可
const f = models.AccountRestrictionFlags.ADDRESS.value; // アドレス制限
const flags = new models.AccountRestrictionFlags(f); // 指定アドレスからのみ受信許可
const accountRestrictionDescriptor =
  new descriptors.AccountAddressRestrictionTransactionV1Descriptor(
    flags,
    [bob.address],
    []
  );
const accountRestrictionEmbeddedTx =
  facade.createEmbeddedTransactionFromTypedDescriptor(
    accountRestrictionDescriptor,
    carol.publicKey
  );

// マルチシグ
const multisigDescriptor =
  new descriptors.MultisigAccountModificationTransactionV1Descriptor(
    1,
    1,
    [bob.address],
    []
  );
const multisigEmbeddedTx = facade.createEmbeddedTransactionFromTypedDescriptor(
  multisigDescriptor,
  carol.publicKey
);

// アグリゲートTx作成
const embeddedTransactions = [accountRestrictionEmbeddedTx, multisigEmbeddedTx];
const aggregateDescriptor =
  new descriptors.AggregateCompleteTransactionV2Descriptor(
    facade.static.hashEmbeddedTransactions(embeddedTransactions),
    embeddedTransactions
  );
const aggregateTx = facade.createTransactionFromTypedDescriptor(
  aggregateDescriptor,
  bob.publicKey,
  100,
  60 * 60 * 2,
  1
) as models.AggregateCompleteTransactionV2;

// 署名
const payloadJson = SymbolTransactionFactory.attachSignature(
  aggregateTx,
  bob.signTransaction(aggregateTx)
);

// 連署
aggregateTx.cosignatures.push(carol.cosignTransaction(aggregateTx, false));

// アナウンス
const response = await fetch(new URL("/transactions", Env.REST_GATEWAY_URL), {
  method: "PUT",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    payload: utils.uint8ToHex(aggregateTx.serialize()),
  }),
});
const responseJson = await response.json();
console.log(responseJson.message);

Alice から Carol へ転送

Carol は Bob からのみ受信できるので、失敗します。

ソース
import {
  KeyPair,
  Network,
  SymbolAccount,
  SymbolFacade,
  SymbolTransactionFactory,
  descriptors,
  models,
} from "symbol-sdk/symbol";
import { PrivateKey } from "symbol-sdk";
import { Env } from "./Env.js";

const facade = new SymbolFacade(Network.TESTNET);
const alicePrikey = new PrivateKey(Env.ALICE_PRIVATE_KEY);
const alice = new SymbolAccount(facade, new KeyPair(alicePrikey));
const carolPrikey = new PrivateKey(Env.CAROL_PRIVATE_KEY);
const carol = new SymbolAccount(facade, new KeyPair(carolPrikey));

// 転送モザイク設定
const sendMosaics = [
  new descriptors.UnresolvedMosaicDescriptor(
    new models.UnresolvedMosaicId(Env.CURRENCY_MOSAIC_ID),
    new models.Amount(100_000000n)
  ),
];

// 平文メッセージ
const messageData = "\0Hello, Carol!!";

// 転送トランザクション生成
const transferDescriptor = new descriptors.TransferTransactionV1Descriptor(
  carol.address,
  sendMosaics,
  messageData
);
const transferTx = facade.createTransactionFromTypedDescriptor(
  transferDescriptor,
  alice.publicKey,
  100,
  60 * 60 * 2
);

// 署名
const payloadJson = SymbolTransactionFactory.attachSignature(
  transferTx,
  alice.signTransaction(transferTx)
);

// アナウンス
const transactionsResponse = await fetch(
  new URL("/transactions", Env.REST_GATEWAY_URL),
  {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: payloadJson,
  }
);
const responseJson = await transactionsResponse.json();
console.log(responseJson.message);

Alice から Carol へアグリゲート転送

Bob への転送に紛れてアグリゲートで Carol に転送しようとしてみても、失敗します。

ソース
import { PrivateKey } from "symbol-sdk";
import {
  SymbolFacade,
  Network,
  SymbolAccount,
  KeyPair,
  descriptors,
  models,
  SymbolTransactionFactory,
} from "symbol-sdk/symbol";
import { Env } from "./Env.js";

const facade = new SymbolFacade(Network.TESTNET);
const alicePrikey = new PrivateKey(Env.ALICE_PRIVATE_KEY);
const alice = new SymbolAccount(facade, new KeyPair(alicePrikey));
const bobPrikey = new PrivateKey(Env.BOB_PRIVATE_KEY);
const bob = new SymbolAccount(facade, new KeyPair(bobPrikey));
const carolPrikey = new PrivateKey(Env.CAROL_PRIVATE_KEY);
const carol = new SymbolAccount(facade, new KeyPair(carolPrikey));

// Bobへの転送
const transferDescriptor1 = new descriptors.TransferTransactionV1Descriptor(
  bob.address,
  [
    new descriptors.UnresolvedMosaicDescriptor(
      new models.UnresolvedMosaicId(Env.CURRENCY_MOSAIC_ID),
      new models.Amount(100_000000n)
    ),
  ],
  "\0Hello, Bob!!"
);
const transferTx1 = facade.createEmbeddedTransactionFromTypedDescriptor(
  transferDescriptor1,
  alice.publicKey
);

// Carolへの転送
const transferDescriptor2 = new descriptors.TransferTransactionV1Descriptor(
  carol.address,
  [
    new descriptors.UnresolvedMosaicDescriptor(
      new models.UnresolvedMosaicId(Env.CURRENCY_MOSAIC_ID),
      new models.Amount(100_000000n)
    ),
  ],
  "\0Hello, Carol!!"
);
const transferTx2 = facade.createEmbeddedTransactionFromTypedDescriptor(
  transferDescriptor2,
  alice.publicKey
);

// アグリゲートTx作成
const embeddedTransactions = [transferTx1, transferTx2];
const aggregateDescriptor =
  new descriptors.AggregateCompleteTransactionV2Descriptor(
    facade.static.hashEmbeddedTransactions(embeddedTransactions),
    embeddedTransactions
  );
const aggregateTx = facade.createTransactionFromTypedDescriptor(
  aggregateDescriptor,
  alice.publicKey,
  100,
  60 * 60 * 2
) as models.AggregateCompleteTransactionV2;

// 署名
const payloadJson = SymbolTransactionFactory.attachSignature(
  aggregateTx,
  alice.signTransaction(aggregateTx)
);

// アナウンス
const response = await fetch(new URL("/transactions", Env.REST_GATEWAY_URL), {
  method: "PUT",
  headers: { "Content-Type": "application/json" },
  body: payloadJson,
});
const responseJson = await response.json();
console.log(responseJson.message);

Alice から Bob へ、Bob から Carol へ、そして Carol から Alice へ

Alice から Bob への転送は許可されているので、Bob が受け取り Carol へ渡します。受け取るかチェックを行いたいので、Alice からはシークレットロックで送信してもらいます。
シークレットロックで送ってもらうことで、Bob が受け取りを拒否した場合、期限が切れるとともに自動的に Alice に返還されます。

  1. Alice から Bob へシークレットロックを送信
  2. Bob はシークレットロックを確認
  3. シークレットロックの内容に問題がない場合プルーフを送信
  4. Bob は Carol にシークレットロック分のモザイクを転送
  5. Bob は Carol に Alice への返事を依頼

3~5 をアグリゲート処理します。
Bob はシークレットのモザイクをそのまま Carol へ渡し、こう返事を出せと命令します。

もはや、Carol は Bob の言いなり…

シークレットロック使用済みや期限切れとかのチェックは省略。

ソース
import { sha3_256 } from "@noble/hashes/sha3";
import { getRandomValues } from "crypto";
import { Hash256, PrivateKey, utils } from "symbol-sdk";
import {
  descriptors,
  KeyPair,
  models,
  Network,
  SymbolAccount,
  SymbolFacade,
  SymbolTransactionFactory,
} from "symbol-sdk/symbol";
import { Env } from "./Env.js";

const facade = new SymbolFacade(Network.TESTNET);
const alicePrikey = new PrivateKey(Env.ALICE_PRIVATE_KEY);
const alice = new SymbolAccount(facade, new KeyPair(alicePrikey));
const bobPrikey = new PrivateKey(Env.BOB_PRIVATE_KEY);
const bob = new SymbolAccount(facade, new KeyPair(bobPrikey));
const carolPrikey = new PrivateKey(Env.CAROL_PRIVATE_KEY);
const carol = new SymbolAccount(facade, new KeyPair(carolPrikey));

/** Alice->BobシークレットTx */
// シークレットとプルーフ作成
const proof = getRandomValues(new Uint8Array(20));
const secretDigest = sha3_256.create().update(proof).digest();
const secret = new Hash256(secretDigest);
console.log(`secret: ${utils.uint8ToHex(secretDigest)}`);
console.log(`proof : ${utils.uint8ToHex(proof)}`);
// シークレットロックトランザクション作成
const secretLockDescriptor = new descriptors.SecretLockTransactionV1Descriptor(
  bob.address,
  secret,
  new descriptors.UnresolvedMosaicDescriptor(
    new models.UnresolvedMosaicId(Env.CURRENCY_MOSAIC_ID),
    new models.Amount(132_000000n)
  ),
  new models.BlockDuration(5n),
  models.LockHashAlgorithm.SHA3_256
);
const secretLockTx = facade.createTransactionFromTypedDescriptor(
  secretLockDescriptor,
  alice.publicKey,
  100,
  60 * 60 * 2
);
// Alice署名
const payloadJson = SymbolTransactionFactory.attachSignature(
  secretLockTx,
  alice.signTransaction(secretLockTx)
);
const secretLockTxHash = facade.hashTransaction(secretLockTx);
// アナウンス
const secretLockResponse = await fetch(
  new URL("/transactions", Env.REST_GATEWAY_URL),
  {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: payloadJson,
  }
);
console.log((await secretLockResponse.json()).message);

/** 承認待機 */
// シークレットTx承認待ち
for (let i = 0; i < 100; i++) {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  const secretTxStatus = await fetch(
    new URL(`/transactionStatus/${secretLockTxHash}`, Env.REST_GATEWAY_URL),
    {
      method: "GET",
      headers: { "Content-Type": "application/json" },
    }
  );
  if ((await secretTxStatus.json()).group === "confirmed") {
    break;
  }
}

/** シークレットロック確認 */
// シークレットロック読み
const secretInfoJson = await fetch(
  new URL(
    `/lock/secret?secret=${utils.uint8ToHex(secretDigest)}`,
    Env.REST_GATEWAY_URL
  ),
  {
    method: "GET",
    headers: { "Content-Type": "application/json" },
  }
);
const secretInfo = await secretInfoJson.json();
const secretMosaicId = new models.UnresolvedMosaicId(
  BigInt("0x" + secretInfo.data[0].lock.mosaicId)
);
const secretAmount = new models.Amount(BigInt(secretInfo.data[0].lock.amount));

/** プルーフと転送Tx送信 */
// シークレットプルーフTx
const embeddedSecretProofDescriptor =
  new descriptors.SecretProofTransactionV1Descriptor(
    bob.address,
    secret,
    models.LockHashAlgorithm.SHA3_256,
    proof
  );
const embeddedSecretProofTx =
  facade.createEmbeddedTransactionFromTypedDescriptor(
    embeddedSecretProofDescriptor,
    bob.publicKey
  );
// Bob->Carol転送Tx
const embeddedTransferDescriptor1 =
  new descriptors.TransferTransactionV1Descriptor(
    carol.address,
    [new descriptors.UnresolvedMosaicDescriptor(secretMosaicId, secretAmount)],
    ""
  );
const embeddedTransferDescriptorTx1 =
  facade.createEmbeddedTransactionFromTypedDescriptor(
    embeddedTransferDescriptor1,
    bob.publicKey
  );
// Carol->Alice転送Tx
const embeddedTransferDescriptor2 =
  new descriptors.TransferTransactionV1Descriptor(
    alice.address,
    [],
    `\0Thank You!! (${secretAmount.value})`
  );
const embeddedTransferDescriptorTx2 =
  facade.createEmbeddedTransactionFromTypedDescriptor(
    embeddedTransferDescriptor2,
    carol.publicKey
  );
// アグリゲートTx作成
const embeddedTransactions = [
  embeddedSecretProofTx,
  embeddedTransferDescriptorTx1,
  embeddedTransferDescriptorTx2,
];
const aggregateDescriptor =
  new descriptors.AggregateCompleteTransactionV2Descriptor(
    facade.static.hashEmbeddedTransactions(embeddedTransactions),
    embeddedTransactions
  );
const aggregateTx = facade.createTransactionFromTypedDescriptor(
  aggregateDescriptor,
  bob.publicKey,
  100,
  60 * 60 * 2,
  1
) as models.AggregateCompleteTransactionV2;
// 署名
const aggregateTxSignature = facade.signTransaction(bob.keyPair, aggregateTx);
const aggregateTxPayloadJson = SymbolTransactionFactory.attachSignature(
  aggregateTx,
  aggregateTxSignature
);
// アグリゲートTxアナウンス
const aggregateResponse = await fetch(
  new URL("/transactions", Env.REST_GATEWAY_URL),
  {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: aggregateTxPayloadJson,
  }
);
console.log((await aggregateResponse.json()).message);

トランザクション

13
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?