LoginSignup
5
3

symbol-sdk@3.2.1でオフラインでアグリゲートコンプリートを完成させる

Last updated at Posted at 2024-05-12

はじめに

今回は、オフラインで、自分と他者の署名が必要なアグリゲートコンプリートトランザクションを作成してみようと思います。

登場人物はこんな感じです。

image.png

この場合、アリスとボブの署名が要ります。

今回のシナリオでは、アリスがトランザクションを作成してボブに渡し、ボブが連署してネットワークに送信するような感じでやっていきます。

image.png

アリスからボブへ渡すデータの形式は、16進数文字列のトランザクションデータにします。

image.png

アリス側

全部

アリスのターン

import {
    PrivateKey,
    PublicKey,
} from 'symbol-sdk';
import {
    Address,
    KeyPair,
    Network,
    SymbolFacade,
} from 'symbol-sdk/symbol'

const ALICE_PRIVATE_KEY = "<alice-private-key>"

const currencyMosaicId = 0x72C0212E67A08BCEn;

const network = Network.TESTNET
const facade = new SymbolFacade(network.name);
const deadline = facade.now().addHours(2).timestamp;

const alicePrivateKey = new PrivateKey(ALICE_PRIVATE_KEY);
const aliceKeyPair = new KeyPair(alicePrivateKey);

const bobPublicKey = new PublicKey('39538DCA630979A96F7003D47E300C3CE06BCDAF3D353F12479FEFD8698C8A9C');

const carolAddress = new Address('TB577ISBQFM6K6I5UM362TTQ573TOL5HWPCDI2A')

const innerTransaction1 = facade.transactionFactory.createEmbedded({
    type: 'transfer_transaction_v1',
    signerPublicKey: aliceKeyPair.publicKey,
    recipientAddress: carolAddress,
    mosaics: [
        { mosaicId: currencyMosaicId, amount: 1000000n },
    ],
});

const innerTransaction2 = facade.transactionFactory.createEmbedded({
    type: 'transfer_transaction_v1',
    signerPublicKey: bobPublicKey,
    recipientAddress: carolAddress,
    mosaics: [
        { mosaicId: currencyMosaicId, amount: 1000000n },
    ],
});

const innerTransactions = [ innerTransaction1, innerTransaction2 ];
const transactionsHash = SymbolFacade.hashEmbeddedTransactions(innerTransactions)

const transaction = facade.transactionFactory.create({
    type: 'aggregate_complete_transaction_v2',
    signerPublicKey: aliceKeyPair.publicKey,
    fee: 1000000n,
    deadline,
    transactions: innerTransactions,
    transactionsHash,
});

const signature = facade.signTransaction(aliceKeyPair, transaction);
const jsonPayloadWithoutCosig = facade.transactionFactory.static.attachSignature(transaction, signature);
const hash = facade.hashTransaction(transaction).toString();

const { payload } = JSON.parse(jsonPayloadWithoutCosig);

console.log("payload", payload)
console.log("hash", hash)

ボブのターン

import {
    PrivateKey,
    utils,
} from 'symbol-sdk';
import {
    KeyPair,
    Network,
    SymbolFacade,
    models,
} from 'symbol-sdk/symbol'

const NODE_URL = "<your-node-url>"
const BOB_PRIVATE_KEY = "<bob-private-key>"

const network = Network.TESTNET
const facade = new SymbolFacade(network.name);

const bobPrivateKey = new PrivateKey(BOB_PRIVATE_KEY);
const bobKeyPair = new KeyPair(bobPrivateKey);

const payload = "680100000000000024140FB446B5C15203BFDF53BA3A3E5895421A3B4603604C33EEBF84AD034968C05FB48FFE1F7CABB768CE31ED0642BC5DC99B851A0A3830E787DEBFC2A7C20497F0F997BB59790239CEF1B284A158EFCD87BF7BBA1E0AFD0B54B26C05D15123000000000298414140420F0000000000A0D1F9EE0A000000FE5806FDC22B3F2650234AD8E83D3CF7FFEC4040FE30003B74C0736BB3603622C000000000000000600000000000000097F0F997BB59790239CEF1B284A158EFCD87BF7BBA1E0AFD0B54B26C05D15123000000000198544198B0BA037DAF763812FFB675B9B5511F81AC0E7284D0A13C0000010000000000CE8BA0672E21C07240420F0000000000600000000000000039538DCA630979A96F7003D47E300C3CE06BCDAF3D353F12479FEFD8698C8A9C0000000001985441987BFFA2418159E5791DA337ED4E70EFF7372FA7B3C434680000010000000000CE8BA0672E21C07280841E0000000000"

const aggregateComplete = models.TransactionFactory.deserialize(utils.hexToUint8(payload));
const bobCosignature = facade.cosignTransaction(bobKeyPair, aggregateComplete);
aggregateComplete.cosignatures.push(bobCosignature);
const jsonPayload = `{"payload":"${utils.uint8ToHex(aggregateComplete.serialize())}"}`;
const hash = facade.hashTransaction(aggregateComplete).toString();

console.log(jsonPayload);
console.log(hash);

const sendRes = await fetch(
    new URL('/transactions', NODE_URL),
    { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: jsonPayload }
)
    .then((res) => res.json());
console.log(sendRes);

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

const statusRes = await fetch(new URL("/transactionStatus/" + hash, NODE_URL))
    .then((res) => res.json());
console.log(statusRes);

詳細

アリスのターン

アリスの手元では、自分の秘密鍵を利用できます。

const alicePrivateKey = new PrivateKey(ALICE_PRIVATE_KEY);
const aliceKeyPair = new KeyPair(alicePrivateKey);

ボブの公開鍵を教えてもらう必要があります。

const bobPublicKey = new PublicKey('39538DCA630979A96F7003D47E300C3CE06BCDAF3D353F12479FEFD8698C8A9C');

キャロルのアドレスを教えてもらう必要があります。

const carolAddress = new Address('TB577ISBQFM6K6I5UM362TTQ573TOL5HWPCDI2A')

というわけで、アリスが知っている情報はこんな感じです。

image.png

アリスからキャロルへのトランザクション

const innerTransaction1 = facade.transactionFactory.createEmbedded({
    type: 'transfer_transaction_v1',
    signerPublicKey: aliceKeyPair.publicKey,
    recipientAddress: carolAddress,
    mosaics: [
        { mosaicId: currencyMosaicId, amount: 1000000n },
    ],
});

image.png

ボブからキャロルへのトランザクション

const innerTransaction2 = facade.transactionFactory.createEmbedded({
    type: 'transfer_transaction_v1',
    signerPublicKey: bobPublicKey,
    recipientAddress: carolAddress,
    mosaics: [
        { mosaicId: currencyMosaicId, amount: 1000000n },
    ],
});

image.png

それらをアグリゲートコンプリートトランザクションに入れ込みます。

const innerTransactions = [ innerTransaction1, innerTransaction2 ];
const transactionsHash = SymbolFacade.hashEmbeddedTransactions(innerTransactions)

const transaction = facade.transactionFactory.create({
    type: 'aggregate_complete_transaction_v2',
    signerPublicKey: aliceKeyPair.publicKey,
    fee: 1000000n,
    deadline,
    transactions: innerTransactions,
    transactionsHash,
});

image.png

アリスはそれに署名して、payloadをボブに渡し、ターンエンドです。

const signature = facade.signTransaction(aliceKeyPair, transaction);
const payloadString = facade.transactionFactory.static.attachSignature(transaction, signature);
const { payload } = JSON.parse(payloadString);

image.png

ボブのターン

アリスからpayloadを受け取ります。

const payload = "680100000000000024140FB446B5C15203BFDF53BA3A3E5895421A3B4603604C33EEBF84AD034968C05FB48FFE1F7CABB768CE31ED0642BC5DC99B851A0A3830E787DEBFC2A7C20497F0F997BB59790239CEF1B284A158EFCD87BF7BBA1E0AFD0B54B26C05D15123000000000298414140420F0000000000A0D1F9EE0A000000FE5806FDC22B3F2650234AD8E83D3CF7FFEC4040FE30003B74C0736BB3603622C000000000000000600000000000000097F0F997BB59790239CEF1B284A158EFCD87BF7BBA1E0AFD0B54B26C05D15123000000000198544198B0BA037DAF763812FFB675B9B5511F81AC0E7284D0A13C0000010000000000CE8BA0672E21C07240420F0000000000600000000000000039538DCA630979A96F7003D47E300C3CE06BCDAF3D353F12479FEFD8698C8A9C0000000001985441987BFFA2418159E5791DA337ED4E70EFF7372FA7B3C434680000010000000000CE8BA0672E21C07280841E0000000000"

これをdeserializeします。

const aggregateComplete = models.AggregateCompleteTransactionV2.deserialize(utils.hexToUint8(payload));

これに連署します。

const bobCosignature = facade.cosignTransaction(bobKeyPair, aggregateComplete);
aggregateComplete.cosignatures.push(bobCosignature);

image.png

これにて、必要な署名が全部集まりました。

ノードにトランザクションを送信するためのデータを作ります。

const jsonPayload = `{"payload":"${utils.uint8ToHex(aggregateComplete.serialize())}"}`;

後で問い合わせするので、トランザクションハッシュを計算しときます。

const hash = facade.hashTransaction(aggregateComplete).toString();

送信します。

console.log(jsonPayload);
console.log(hash);

const sendRes = await fetch(
    new URL('/transactions', NODE_URL),
    { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: jsonPayload }
)
    .then((res) => res.json());
console.log(sendRes);

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

const statusRes = await fetch(new URL("/transactionStatus/" + hash, NODE_URL))
    .then((res) => res.json());
console.log(statusRes);

まとめ

今回大切なところはここだけです。

const aggregateComplete = models.TransactionFactory.deserialize(utils.hexToUint8(payload));

16進数文字列のトランザクションを、トランザクションのインスタンスに変換しているところです。

5
3
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
5
3