コンテンツにスキップ
中級

転送トランザクションでのメッセージの送信⚓︎

転送トランザクション には、オプションのメッセージフィールドを含めることができ、トランザクションに最大1,024バイトのデータを添付できます。 メッセージはプレーンテキストとして送信することも、受信者の公開鍵を使用して暗号化して送信することもでき、意図した受信者のみが読み取れるようにすることができます。

このチュートリアルでは、プレーンテキストと暗号化されたメッセージの両方を送信する方法と、受信したメッセージをデコードする方法を説明します。

前提条件⚓︎

開始する前に、以下を確認してください。

さらに、手数料の計算、ネットワーク時間、およびトランザクションの承認がどのように機能するかを理解するために、転送トランザクション チュートリアルを確認してください。

完全なコード⚓︎

このチュートリアルの完全なコード一覧を以下に示します。 詳細な手順ごとの説明は次のセクションで行います。

import json
import os
import time
import urllib.error
import urllib.request
from binascii import hexlify

from symbolchain.CryptoTypes import PrivateKey, PublicKey
from symbolchain.facade.SymbolFacade import SymbolFacade
from symbolchain.symbol.MessageEncoder import MessageEncoder
from symbolchain.symbol.Network import NetworkTimestamp
from symbolchain.sc import Amount

# Configuration
NODE_URL = os.getenv(
    "NODE_URL", "https://reference.symboltest.net:3001"
)
print(f"Using node {NODE_URL}")

# Helper function to poll for confirmed transaction
def retrieve_confirmed_transaction(hash_value, label):
    print(f"Polling for {label} confirmation...")
    confirmed = False
    attempts = 0
    max_attempts = 60

    while not confirmed and attempts < max_attempts:
        try:
            url = f"{NODE_URL}/transactions/confirmed/{hash_value}"
            with urllib.request.urlopen(url) as response:
                confirmed = True
                print(f"  {label} confirmed!")
                return json.loads(response.read().decode())
        except urllib.error.HTTPError:
            # Transaction not yet confirmed
            pass
        attempts += 1
        time.sleep(2)

    if not confirmed:
        raise Exception(
            f"{label} not confirmed after {max_attempts} attempts"
        )

# Set up sender and recipient accounts
facade = SymbolFacade("testnet")

sender_private_key_string = os.getenv(
    "SENDER_PRIVATE_KEY",
    "0000000000000000000000000000000000000000000000000000000000000000",
)
sender_key_pair = facade.KeyPair(
    PrivateKey(sender_private_key_string)
)
sender_address = facade.network.public_key_to_address(
    sender_key_pair.public_key
)

recipient_private_key_string = os.getenv(
    "RECIPIENT_PRIVATE_KEY",
    "1111111111111111111111111111111111111111111111111111111111111111",
)
recipient_key_pair = facade.KeyPair(
    PrivateKey(recipient_private_key_string)
)
recipient_address = facade.network.public_key_to_address(
    recipient_key_pair.public_key
)

print(f"Sender address: {sender_address}")
print(f"Recipient address: {recipient_address}\n")

# Fetch current network time
time_path = "/node/time"
print(f"Fetching current network time from {time_path}")
with urllib.request.urlopen(f"{NODE_URL}{time_path}") as response:
    response_json = json.loads(response.read().decode())
    timestamp = NetworkTimestamp(int(
        response_json['communicationTimestamps']['receiveTimestamp'])
    )
    print(f"  Network time: {timestamp.timestamp} ms since nemesis")

# Fetch recommended fees
fee_path = "/network/fees/transaction"
print(f"Fetching recommended fees from {fee_path}")
with urllib.request.urlopen(f"{NODE_URL}{fee_path}") as response:
    response_json = json.loads(response.read().decode())
    median_mult = response_json["medianFeeMultiplier"]
    minimum_mult = response_json["minFeeMultiplier"]
    fee_mult = max(median_mult, minimum_mult)
    print(f"  Fee multiplier: {fee_mult}\n")

# ===== PLAIN TEXT MESSAGE =====
print("==> Sending Plain Text Message")

# Create a plain text message
plain_message = "Hello, Symbol!".encode("utf-8")
print(f"Plain message: {plain_message.decode('utf-8')}")

# Build transfer transaction with plain message
plain_transaction = facade.transaction_factory.create(
    {
        "type": "transfer_transaction_v1",
        "signer_public_key": sender_key_pair.public_key,
        "deadline": timestamp.add_hours(2).timestamp,
        "recipient_address": recipient_address,
        "mosaics": [],
        "message": plain_message,
    }
)
plain_transaction.fee = Amount(fee_mult * plain_transaction.size)

# Sign and announce the transaction
plain_signature = facade.sign_transaction(
    sender_key_pair, plain_transaction
)
plain_json_payload = facade.transaction_factory.attach_signature(
    plain_transaction, plain_signature
)
plain_transaction_hash = facade.hash_transaction(
    plain_transaction
)
print(f"Transaction hash: {plain_transaction_hash}")

plain_announce_request = urllib.request.Request(
    f"{NODE_URL}/transactions",
    data=plain_json_payload.encode("utf-8"),
    headers={"Content-Type": "application/json"},
    method="PUT",
)
with urllib.request.urlopen(plain_announce_request) as response:
    print(f"Plain message transaction announced\n")

# ===== RECEIVING PLAIN TEXT MESSAGE =====
print("<== Receiving Plain Text Message")

# Wait for confirmation
plain_tx_data = retrieve_confirmed_transaction(
    plain_transaction_hash, "Plain message transaction"
)

# Decode plain message from confirmed transaction
received_plain_message = bytes.fromhex(
    plain_tx_data["transaction"]["message"]
)
print(
    f"Received plain message: {received_plain_message.decode('utf-8')}\n"
)

# ===== ENCRYPTED MESSAGE =====
print("==> Sending Encrypted Message")

# Create a message encoder with sender's key pair
sender_message_encoder = MessageEncoder(sender_key_pair)

# Encrypt the message using recipient's public key
secret_message = "This is a secret message!".encode("utf-8")
encrypted_payload = sender_message_encoder.encode(
    recipient_key_pair.public_key, secret_message
)
print(f"Original message: {secret_message.decode('utf-8')}")
print(
    "Encrypted payload: "
    + hexlify(encrypted_payload).decode("utf-8")
)

# Build transfer transaction with encrypted message
encrypted_transaction = facade.transaction_factory.create(
    {
        "type": "transfer_transaction_v1",
        "signer_public_key": sender_key_pair.public_key,
        "deadline": timestamp.add_hours(2).timestamp,
        "recipient_address": recipient_address,
        "mosaics": [],
        "message": encrypted_payload,
    }
)
encrypted_transaction.fee = Amount(fee_mult * encrypted_transaction.size)

# Sign and announce the transaction
encrypted_signature = facade.sign_transaction(
    sender_key_pair, encrypted_transaction
)
encrypted_json_payload = facade.transaction_factory.attach_signature(
    encrypted_transaction, encrypted_signature
)
encrypted_transaction_hash = facade.hash_transaction(
    encrypted_transaction
)
print(f"Transaction hash: {encrypted_transaction_hash}")

encrypted_announce_request = urllib.request.Request(
    f"{NODE_URL}/transactions",
    data=encrypted_json_payload.encode("utf-8"),
    headers={"Content-Type": "application/json"},
    method="PUT",
)
with urllib.request.urlopen(encrypted_announce_request) as response:
    print(f"Encrypted message transaction announced\n")

# ===== RECEIVING ENCRYPTED MESSAGE =====
print("<== Receiving Encrypted Message")

# Wait for confirmation
encrypted_tx_data = retrieve_confirmed_transaction(
    encrypted_transaction_hash, "Encrypted message transaction"
)

# Decode encrypted message using recipient's private key
recipient_message_encoder = MessageEncoder(recipient_key_pair)
received_encrypted_message = bytes.fromhex(
    encrypted_tx_data["transaction"]["message"]
)

# Get sender's public key from the transaction
sender_public_key_from_tx = PublicKey(
    encrypted_tx_data["transaction"]["signerPublicKey"]
)

(is_decoded, decrypted_message) = recipient_message_encoder.try_decode(
    sender_public_key_from_tx, received_encrypted_message
)

if is_decoded:
    message_text = decrypted_message.decode("utf-8")
    print(f"Recipient decrypted message: {message_text}")
else:
    print(f"Recipient failed to decrypt message")

Download source

import { PrivateKey, PublicKey } from 'symbol-sdk';
import {
    SymbolFacade,
    NetworkTimestamp,
    models,
    MessageEncoder
} from 'symbol-sdk/symbol';

// Configuration
const NODE_URL = process.env.NODE_URL ||
    'https://reference.symboltest.net:3001';
console.log('Using node', NODE_URL);

// Helper function to poll for confirmed transaction
async function retrieveConfirmedTransaction(hash, label) {
    console.log(`Polling for ${label} confirmation...`);
    let confirmed = false;
    let attempts = 0;
    const maxAttempts = 60;

    while (!confirmed && attempts < maxAttempts) {
        try {
            const response = await fetch(
                `${NODE_URL}/transactions/confirmed/${hash}`);
            if (response.ok) {
                confirmed = true;
                console.log(`  ${label} confirmed!`);
                return await response.json();
            }
        } catch (error) {
            // Transaction not yet confirmed
        }
        attempts++;
        await new Promise(resolve => setTimeout(resolve, 2000));
    }

    if (!confirmed) {
        throw new Error(
            `${label} not confirmed after ${maxAttempts} attempts`);
    }
}

// Set up sender and recipient accounts
const facade = new SymbolFacade('testnet');

const senderPrivateKeyString = process.env.SENDER_PRIVATE_KEY ||
    '0000000000000000000000000000000000000000000000000000000000000000';
const senderKeyPair = new SymbolFacade.KeyPair(
    new PrivateKey(senderPrivateKeyString));
const senderAddress = facade.network.publicKeyToAddress(
    senderKeyPair.publicKey);

const recipientPrivateKeyString = process.env.RECIPIENT_PRIVATE_KEY ||
    '1111111111111111111111111111111111111111111111111111111111111111';
const recipientKeyPair = new SymbolFacade.KeyPair(
    new PrivateKey(recipientPrivateKeyString));
const recipientAddress = facade.network.publicKeyToAddress(
    recipientKeyPair.publicKey);

console.log('Sender address:', senderAddress.toString());
console.log('Recipient address:', recipientAddress.toString(), '\n');

// Fetch current network time
const timePath = '/node/time';
console.log('Fetching current network time from', timePath);
const timeResponse = await fetch(`${NODE_URL}${timePath}`);
const timeJSON = await timeResponse.json();
const timestamp = new NetworkTimestamp(
    timeJSON.communicationTimestamps.receiveTimestamp);
console.log('  Network time:', timestamp.timestamp,
    'ms since nemesis');

// Fetch recommended fees
const feePath = '/network/fees/transaction';
console.log('Fetching recommended fees from', feePath);
const feeResponse = await fetch(`${NODE_URL}${feePath}`);
const feeJSON = await feeResponse.json();
const medianMult = feeJSON.medianFeeMultiplier;
const minimumMult = feeJSON.minFeeMultiplier;
const feeMult = Math.max(medianMult, minimumMult);
console.log('  Fee multiplier:', feeMult, '\n');

// ===== PLAIN TEXT MESSAGE =====
console.log('==> Sending Plain Text Message');

// Create a plain text message
const plainMessage = new TextEncoder().encode('Hello, Symbol!');
console.log('Plain message:',
    new TextDecoder().decode(plainMessage));

// Build transfer transaction with plain message
const plainTransaction = facade.transactionFactory.create({
    type: 'transfer_transaction_v1',
    signerPublicKey: senderKeyPair.publicKey.toString(),
    deadline: timestamp.addHours(2).timestamp,
    recipientAddress: recipientAddress.toString(),
    mosaics: [],
    message: plainMessage
});
plainTransaction.fee = new models.Amount(feeMult * plainTransaction.size);

// Sign and announce the transaction
const plainSignature = facade.signTransaction(
    senderKeyPair, plainTransaction);
const plainJsonPayload = facade.transactionFactory.static
    .attachSignature(plainTransaction, plainSignature);
const plainTransactionHash = facade.hashTransaction(
    plainTransaction).toString();
console.log('Transaction hash:', plainTransactionHash);

await fetch(`${NODE_URL}/transactions`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: plainJsonPayload
});
console.log('Plain message transaction announced\n');

// ===== RECEIVING PLAIN TEXT MESSAGE =====
console.log('<== Receiving Plain Text Message');

// Wait for confirmation
const plainTxData = await retrieveConfirmedTransaction(
    plainTransactionHash, 'Plain message transaction');

// Decode plain message from confirmed transaction
const receivedPlainMessage = Buffer.from(
    plainTxData.transaction.message, 'hex');
console.log('Received plain message:',
    new TextDecoder().decode(receivedPlainMessage), '\n');

// ===== ENCRYPTED MESSAGE =====
console.log('==> Sending Encrypted Message');

// Create a message encoder with sender's key pair
const senderMessageEncoder = new MessageEncoder(senderKeyPair);

// Encrypt the message using recipient's public key
const secretMessage = new TextEncoder().encode(
    'This is a secret message!');
const encryptedPayload = senderMessageEncoder.encode(
    recipientKeyPair.publicKey, secretMessage
);
console.log('Original message:',
    new TextDecoder().decode(secretMessage));
const hex = Buffer.from(encryptedPayload).toString('hex');
console.log('Encrypted payload:', hex);

// Build transfer transaction with encrypted message
const encryptedTransaction = facade.transactionFactory.create({
    type: 'transfer_transaction_v1',
    signerPublicKey: senderKeyPair.publicKey.toString(),
    deadline: timestamp.addHours(2).timestamp,
    recipientAddress: recipientAddress.toString(),
    mosaics: [],
    message: encryptedPayload
});
encryptedTransaction.fee = new models.Amount(
    feeMult * encryptedTransaction.size);

// Sign and announce the transaction
const encryptedSignature = facade.signTransaction(
    senderKeyPair, encryptedTransaction);
const encryptedJsonPayload = facade.transactionFactory.static
.attachSignature(encryptedTransaction, encryptedSignature);
const encryptedTransactionHash = facade.hashTransaction(
    encryptedTransaction).toString();
console.log('Transaction hash:', encryptedTransactionHash);

await fetch(`${NODE_URL}/transactions`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: encryptedJsonPayload
});
console.log('Encrypted message transaction announced\n');

// ===== RECEIVING ENCRYPTED MESSAGE =====
console.log('<== Receiving Encrypted Message');

// Wait for confirmation
const encryptedTxData = await retrieveConfirmedTransaction(
    encryptedTransactionHash, 'Encrypted message transaction');

// Decode encrypted message using recipient's private key
const recipientMessageEncoder = new MessageEncoder(recipientKeyPair);
const receivedEncryptedMessage = Buffer.from(
    encryptedTxData.transaction.message, 'hex');

// Get sender's public key from the transaction
const senderPublicKeyFromTx = new PublicKey(
    encryptedTxData.transaction.signerPublicKey);

const result = recipientMessageEncoder.tryDecode(
    senderPublicKeyFromTx, receivedEncryptedMessage);

if (result.isDecoded) {
    console.log('Recipient decrypted message:',
        new TextDecoder().decode(result.message));
} else {
    console.log('Recipient failed to decrypt message');
}

Download source

コード解説⚓︎

このチュートリアルでは、転送トランザクションのメッセージ固有の側面に焦点を当てます。 ネットワーク時間の取得、手数料の計算、トランザクションのアナウンスに関する部分は、 転送トランザクション のチュートリアルで説明しているため、ここでは省略します。

アカウントの設定⚓︎

# Set up sender and recipient accounts
facade = SymbolFacade("testnet")

sender_private_key_string = os.getenv(
    "SENDER_PRIVATE_KEY",
    "0000000000000000000000000000000000000000000000000000000000000000",
)
sender_key_pair = facade.KeyPair(
    PrivateKey(sender_private_key_string)
)
sender_address = facade.network.public_key_to_address(
    sender_key_pair.public_key
)

recipient_private_key_string = os.getenv(
    "RECIPIENT_PRIVATE_KEY",
    "1111111111111111111111111111111111111111111111111111111111111111",
)
recipient_key_pair = facade.KeyPair(
    PrivateKey(recipient_private_key_string)
)
recipient_address = facade.network.public_key_to_address(
    recipient_key_pair.public_key
)

print(f"Sender address: {sender_address}")
print(f"Recipient address: {recipient_address}\n")
// Set up sender and recipient accounts
const facade = new SymbolFacade('testnet');

const senderPrivateKeyString = process.env.SENDER_PRIVATE_KEY ||
    '0000000000000000000000000000000000000000000000000000000000000000';
const senderKeyPair = new SymbolFacade.KeyPair(
    new PrivateKey(senderPrivateKeyString));
const senderAddress = facade.network.publicKeyToAddress(
    senderKeyPair.publicKey);

const recipientPrivateKeyString = process.env.RECIPIENT_PRIVATE_KEY ||
    '1111111111111111111111111111111111111111111111111111111111111111';
const recipientKeyPair = new SymbolFacade.KeyPair(
    new PrivateKey(recipientPrivateKeyString));
const recipientAddress = facade.network.publicKeyToAddress(
    recipientKeyPair.publicKey);

console.log('Sender address:', senderAddress.toString());
console.log('Recipient address:', recipientAddress.toString(), '\n');

メッセージを送信するには、送信者の 秘密鍵 と受信者のアドレス が必要です。 メッセージを暗号化するには、さらに受信者の 公開鍵 が必要です。

このチュートリアルでは、プレーンテキストおよび暗号化されたメッセージの送受信の両方を実演するために、2つのアカウント(送信者と受信者)を使用します。 スニペットは、 SENDER_PRIVATE_KEY および RECIPIENT_PRIVATE_KEY 環境変数から秘密鍵を読み取ります。これらが設定されていない場合は、デフォルトのテストキーが使用されます。 受信者の公開鍵とアドレスは、その秘密鍵から派生します。

公開鍵の取得

アドレスのみがわかっている場合は、 /accounts/{accountId} GET エンドポイントを使用してネットワークから公開鍵を取得できます。 アカウントの公開鍵は、そのアカウントが少なくとも1つのトランザクションをブロードキャストした後に初めて利用可能になります。

プレーンテキストメッセージの送信⚓︎

print("==> Sending Plain Text Message")

# Create a plain text message
plain_message = "Hello, Symbol!".encode("utf-8")
print(f"Plain message: {plain_message.decode('utf-8')}")

# Build transfer transaction with plain message
plain_transaction = facade.transaction_factory.create(
    {
        "type": "transfer_transaction_v1",
        "signer_public_key": sender_key_pair.public_key,
        "deadline": timestamp.add_hours(2).timestamp,
        "recipient_address": recipient_address,
        "mosaics": [],
        "message": plain_message,
    }
)
console.log('==> Sending Plain Text Message');

// Create a plain text message
const plainMessage = new TextEncoder().encode('Hello, Symbol!');
console.log('Plain message:',
    new TextDecoder().decode(plainMessage));

// Build transfer transaction with plain message
const plainTransaction = facade.transactionFactory.create({
    type: 'transfer_transaction_v1',
    signerPublicKey: senderKeyPair.publicKey.toString(),
    deadline: timestamp.addHours(2).timestamp,
    recipientAddress: recipientAddress.toString(),
    mosaics: [],
    message: plainMessage
});

トランザクション記述子に mosaics フィールドと message フィールドの両方を含めることで、モザイクの転送とメッセージを組み合わせることができます。

その後、トランザクションは 転送トランザクションの作成 と同じプロセスに従って署名され、アナウンスされます。

メッセージの制約:

  • 最大サイズ: 1,024バイト(これより大きいメッセージはネットワークによって拒否されます)。
  • エンコーディング: 慣習的にUTF-8が使用されますが、プロトコルによって標準が強制されるわけではありません。
  • プライバシー: 暗号化されていない限り、すべてのメッセージはブロックチェーン上で公開されます。

より大きなデータの処理

1,024バイトを超えるデータを必要とするアプリケーションの場合、一般的なアプローチには以下が含まれます。

  • オンチェーンストレージ: データを アグリゲートトランザクション 内の複数のトランザクションに分割し、すべてをブロックチェーン上に保持できるようにします。
  • オフチェーンストレージ: データをオフチェーンに保存し、メッセージフィールドにハッシュと参照を含めます。 ハッシュによってデータの完全性を検証し、参照によってデータの取得を可能にします。

プレーンテキストメッセージの受信⚓︎

print("<== Receiving Plain Text Message")

# Wait for confirmation
plain_tx_data = retrieve_confirmed_transaction(
    plain_transaction_hash, "Plain message transaction"
)

# Decode plain message from confirmed transaction
received_plain_message = bytes.fromhex(
    plain_tx_data["transaction"]["message"]
)
print(
    f"Received plain message: {received_plain_message.decode('utf-8')}\n"
)
console.log('<== Receiving Plain Text Message');

// Wait for confirmation
const plainTxData = await retrieveConfirmedTransaction(
    plainTransactionHash, 'Plain message transaction');

// Decode plain message from confirmed transaction
const receivedPlainMessage = Buffer.from(
    plainTxData.transaction.message, 'hex');
console.log('Received plain message:',
    new TextDecoder().decode(receivedPlainMessage), '\n');

トランザクションをアナウンスした後、 retrieve_confirmed_transaction ヘルパー関数が、トランザクションが承認されるまで /transactions/confirmed/{transactionId} GET エンドポイントをポーリングします。

承認されたトランザクションには、メッセージが16進数文字列として含まれています。 元のメッセージを取得するには、16進数文字列をバイトに変換し、UTF-8としてデコードします。

暗号化されたメッセージの送信⚓︎

print("==> Sending Encrypted Message")

# Create a message encoder with sender's key pair
sender_message_encoder = MessageEncoder(sender_key_pair)

# Encrypt the message using recipient's public key
secret_message = "This is a secret message!".encode("utf-8")
encrypted_payload = sender_message_encoder.encode(
    recipient_key_pair.public_key, secret_message
)
print(f"Original message: {secret_message.decode('utf-8')}")
print(
    "Encrypted payload: "
    + hexlify(encrypted_payload).decode("utf-8")
)

# Build transfer transaction with encrypted message
encrypted_transaction = facade.transaction_factory.create(
    {
        "type": "transfer_transaction_v1",
        "signer_public_key": sender_key_pair.public_key,
        "deadline": timestamp.add_hours(2).timestamp,
        "recipient_address": recipient_address,
        "mosaics": [],
        "message": encrypted_payload,
    }
)
console.log('==> Sending Encrypted Message');

// Create a message encoder with sender's key pair
const senderMessageEncoder = new MessageEncoder(senderKeyPair);

// Encrypt the message using recipient's public key
const secretMessage = new TextEncoder().encode(
    'This is a secret message!');
const encryptedPayload = senderMessageEncoder.encode(
    recipientKeyPair.publicKey, secretMessage
);
console.log('Original message:',
    new TextDecoder().decode(secretMessage));
const hex = Buffer.from(encryptedPayload).toString('hex');
console.log('Encrypted payload:', hex);

// Build transfer transaction with encrypted message
const encryptedTransaction = facade.transactionFactory.create({
    type: 'transfer_transaction_v1',
    signerPublicKey: senderKeyPair.publicKey.toString(),
    deadline: timestamp.addHours(2).timestamp,
    recipientAddress: recipientAddress.toString(),
    mosaics: [],
    message: encryptedPayload
});

暗号化されたメッセージは、送信者の秘密鍵と受信者の公開鍵から派生した共通鍵を使用してメッセージの内容を保護することで、機密性を提供します。 送信者と受信者の両方が、自身の秘密鍵と相手の公開鍵を使用してメッセージを復号できます。

クラスがメッセージの暗号化を処理します。

  1. 送信者のキーペアを使用して が作成されます。
  2. 受信者の公開鍵とメッセージバイトを使用し、 によってメッセージがエンコードされます。
  3. 暗号化されたペイロードがトランザクションの message フィールドに添付されます。

その後、トランザクションは 転送トランザクションの作成 と同じプロセスに従って署名され、アナウンスされます。

メッセージの暗号化は慣習です

Symbolプロトコルはメッセージ暗号化の標準を定義していません。 送信者と受信者は、メッセージを暗号化するかどうか、および使用する暗号について事前に合意しておく必要があります。 クラスは、ほとんどのウォレットやアプリケーションで使用されている、広く採用された慣習を実装しています。

詳細については、テキストブックの オプションのメッセージ を参照してください。

暗号化されたメッセージの受信⚓︎

print("<== Receiving Encrypted Message")

# Wait for confirmation
encrypted_tx_data = retrieve_confirmed_transaction(
    encrypted_transaction_hash, "Encrypted message transaction"
)

# Decode encrypted message using recipient's private key
recipient_message_encoder = MessageEncoder(recipient_key_pair)
received_encrypted_message = bytes.fromhex(
    encrypted_tx_data["transaction"]["message"]
)

# Get sender's public key from the transaction
sender_public_key_from_tx = PublicKey(
    encrypted_tx_data["transaction"]["signerPublicKey"]
)

(is_decoded, decrypted_message) = recipient_message_encoder.try_decode(
    sender_public_key_from_tx, received_encrypted_message
)

if is_decoded:
    message_text = decrypted_message.decode("utf-8")
    print(f"Recipient decrypted message: {message_text}")
else:
    print(f"Recipient failed to decrypt message")
console.log('<== Receiving Encrypted Message');

// Wait for confirmation
const encryptedTxData = await retrieveConfirmedTransaction(
    encryptedTransactionHash, 'Encrypted message transaction');

// Decode encrypted message using recipient's private key
const recipientMessageEncoder = new MessageEncoder(recipientKeyPair);
const receivedEncryptedMessage = Buffer.from(
    encryptedTxData.transaction.message, 'hex');

// Get sender's public key from the transaction
const senderPublicKeyFromTx = new PublicKey(
    encryptedTxData.transaction.signerPublicKey);

const result = recipientMessageEncoder.tryDecode(
    senderPublicKeyFromTx, receivedEncryptedMessage);

if (result.isDecoded) {
    console.log('Recipient decrypted message:',
        new TextDecoder().decode(result.message));
} else {
    console.log('Recipient failed to decrypt message');
}

暗号化されたメッセージトランザクションをアナウンスした後、 retrieve_confirmed_transaction ヘルパー関数が承認をポーリングします。

承認されたトランザクションからメッセージを復号するには、受信者のキーペアを使用して を作成し、送信者の公開鍵(トランザクションの signerPublicKey フィールドから取得)と暗号化されたペイロードを指定して を呼び出します。

このメソッドは、復号に成功したかどうかを示すタプル (is_decoded, message) を返します。成功した場合、元のプレーンテキストのバイトが含まれますが、これらはまだデコードする必要があります。

復号は双方向で機能します

暗号化には両方のキーペアから派生した共有シークレットが使用されるため、送信者も自身の秘密鍵と受信者の公開鍵を使用してメッセージを復号できます。 これにより、両者がブロックチェーン上に公開された後のメッセージ内容を検証できます。

復号に失敗した場合、考えられる原因は以下の通りです。

  • メッセージが別の受信者向けに暗号化されている。
  • メッセージが破損しているか、改ざんされている。
  • メッセージが暗号化されていないプレーンテキストである。
  • 相手の公開鍵が間違っている。

出力⚓︎

以下に示す出力は、プログラムの典型的な実行結果に対応しています。

Using node https://reference.symboltest.net:3001
Sender address: TCHBDENCLKEBILBPWP3JPB2XNY64OE7PYHHE32I
Recipient address: TCWYXKVYBMO4NBCUF3AXKJMXCGVSYQOS7ZG2TLI

Fetching current network time from /node/time
  Network time: 93047688268 ms since nemesis
Fetching recommended fees from /network/fees/transaction
  Fee multiplier: 100

==> Sending Plain Text Message
Plain message: Hello, Symbol!
Transaction hash: F30D4F2D20CDF46EC2A5D3D05E3A1059A18BD6D66C661A3DD66A7946612E857A
Plain message transaction announced

<== Receiving Plain Text Message
Polling for Plain message transaction confirmation...
  Plain message transaction confirmed!
Received plain message: Hello, Symbol!

==> Sending Encrypted Message
Original message: This is a secret message!
Encrypted payload: 01c1cc6085863447340555ba03d319e9b5e64c1619ff9fb6ed20a9e823f121056e0d96927df2a523df3f4e9663eb5981e2542528cb49
Transaction hash: 8E4582520E9BD1C4AC1E758BE49BFDF8719F9563676289D81A00E5532C8FA86D
Encrypted message transaction announced

<== Receiving Encrypted Message
Polling for Encrypted message transaction confirmation...
  Encrypted message transaction confirmed!
Recipient decrypted message: This is a secret message!

出力に表示されたトランザクションハッシュを検索することで、 Symbol Testnet Explorer でトランザクションを確認できます。

エクスプローラーは秘密鍵にアクセスできないため、暗号化されたメッセージを復号することはできません。

結論⚓︎

このチュートリアルでは、以下の方法を説明しました。

ステップ 関連ドキュメント
テキストをUTF-8バイトに変換する TextEncoder (JS) および str.encode/bytes.decode (Python)
システムメソッドであり、Symbol SDKの一部ではありません
メッセージの暗号化と復号
転送トランザクションにメッセージを含める