コンテンツにスキップ

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

中級

転送トランザクション には、オプションのメッセージフィールドを含めることができ、トランザクションに最大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

# 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...")
    attempts = 0
    max_attempts = 60

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

    raise TimeoutError(
        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 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_multiplier = response_json["medianFeeMultiplier"]
    minimum_multiplier = response_json["minFeeMultiplier"]
    fee_multiplier = max(median_multiplier, minimum_multiplier)
    print(f"  Fee multiplier: {fee_multiplier}\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.create_transaction_from_descriptor(
    {
        "type": "transfer_transaction_v1",
        "recipient_address": recipient_address,
        "mosaics": [],
        "message": plain_message,
    },
    sender_key_pair.public_key,
    fee_multiplier,
    2 * 60 * 60)

# 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("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(f"Encrypted payload: {hexlify(encrypted_payload).decode("utf-8")}")

# Build transfer transaction with encrypted message
encrypted_transaction = facade.create_transaction_from_descriptor(
    {
        "type": "transfer_transaction_v1",
        "recipient_address": recipient_address,
        "mosaics": [],
        "message": encrypted_payload,
    },
    sender_key_pair.public_key,
    fee_multiplier,
    2 * 60 * 60)

# 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("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("Recipient failed to decrypt message")

Download source

import { PrivateKey, PublicKey } from 'symbol-sdk';
import {
    MessageEncoder,
    SymbolFacade,
    descriptors
} 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 attempts = 0;
    const maxAttempts = 60;

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

    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 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 medianMultiplier = feeJSON.medianFeeMultiplier;
const minimumMultiplier = feeJSON.minFeeMultiplier;
const feeMultiplier = Math.max(medianMultiplier, minimumMultiplier);
console.log('  Fee multiplier:', feeMultiplier, '\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.createTransactionFromTypedDescriptor(
    new descriptors.TransferTransactionV1Descriptor(
        recipientAddress,
        [],
        plainMessage
    ),
    senderKeyPair.publicKey,
    feeMultiplier,
    2 * 60 * 60);

// 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));
console.log('Encrypted payload:',
    Buffer.from(encryptedPayload).toString('hex'));

// Build transfer transaction with encrypted message
const encryptedTransaction = facade.createTransactionFromTypedDescriptor(
    new descriptors.TransferTransactionV1Descriptor(
        recipientAddress,
        [],
        encryptedPayload
    ),
    senderKeyPair.publicKey,
    feeMultiplier,
    2 * 60 * 60);

// 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

//JAVA 21+
//DEPS org.symbol:symbol-sdk:3.3.1

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.symbol.sdk.CryptoTypes;
import org.symbol.sdk.MessageEncoderResult;
import org.symbol.sdk.facade.SymbolFacade;
import org.symbol.sdk.symbol.Address;
import org.symbol.sdk.symbol.KeyPair;
import org.symbol.sdk.symbol.MessageEncoder;
import org.symbol.sdk.symbol.SymbolTransactionFactory;
import org.symbol.sdk.symbol.descriptors.*;
import org.symbol.sdk.symbol.models.Transaction;

public final class Messages {
    private static final ObjectMapper JSON_MAPPER = new ObjectMapper();

    private static final HttpClient HTTP_CLIENT =
        HttpClient.newHttpClient();

    // Configuration
    private final String nodeUrl = System.getenv().getOrDefault(
        "NODE_URL", "https://reference.symboltest.net:3001");

    private final SymbolFacade facade = new SymbolFacade("testnet");

    // Helper function to poll for confirmed transaction
    private JsonNode retrieveConfirmedTransaction(
        final String hash,
        final String label
    ) throws IOException, InterruptedException {
        System.out.printf("Polling for %s confirmation...%n", label);
        int attempts = 0;
        final int maxAttempts = 60;

        while (attempts < maxAttempts) {
            try {
                final HttpRequest transactionConfirmed =
                    HttpRequest.newBuilder(URI.create(
                        nodeUrl + "/transactions/confirmed/" + hash))
                        .GET()
                        .build();
                final HttpResponse<String> response = HTTP_CLIENT.send(
                    transactionConfirmed, BodyHandlers.ofString());
                if (response.statusCode() / 100 == 2) {
                    System.out.printf("  %s confirmed!%n", label);
                    return JSON_MAPPER.readTree(response.body());
                }
            } catch (final IOException ex) {
                // Transaction not yet confirmed
            }
            ++attempts;
            Thread.sleep(2000);
        }

        throw new IOException(String.format(
            "%s not confirmed after %d attempts", label, maxAttempts));
    }

    public static void main(final String[] args) {
        try {
            new Messages().run();
        } catch (final Exception ex) {
            System.out.println(null == ex.getMessage()
                ? ex.toString()
                : ex.getMessage());
        }
    }

    private void run() throws IOException, InterruptedException {
        System.out.printf("Using node %s%n", nodeUrl);

        // Set up sender and recipient accounts
        final String senderPrivateKeyString = System.getenv().getOrDefault(
            "SENDER_PRIVATE_KEY", "0".repeat(64));
        final KeyPair senderKeyPair = new KeyPair(
            new CryptoTypes.PrivateKey(senderPrivateKeyString));
        final Address senderAddress = facade.network.publicKeyToAddress(
            senderKeyPair.getPublicKey());

        final String recipientPrivateKeyString = System.getenv()
            .getOrDefault("RECIPIENT_PRIVATE_KEY", "1".repeat(64));
        final KeyPair recipientKeyPair = new KeyPair(
            new CryptoTypes.PrivateKey(recipientPrivateKeyString));
        final Address recipientAddress = facade.network.publicKeyToAddress(
            recipientKeyPair.getPublicKey());

        System.out.printf("Sender address: %s%n", senderAddress);
        System.out.printf("Recipient address: %s%n%n", recipientAddress);

        // Fetch recommended fees
        final String feePath = "/network/fees/transaction";
        System.out.printf(
            "Fetching recommended fees from %s%n", feePath);
        final HttpRequest feeRequest = HttpRequest.newBuilder(
            URI.create(nodeUrl + feePath)).GET().build();
        final HttpResponse<String> feeResponse = HTTP_CLIENT.send(
            feeRequest, BodyHandlers.ofString());
        final JsonNode feeJSON = JSON_MAPPER.readTree(
            feeResponse.body());
        final long medianMultiplier =
            feeJSON.get("medianFeeMultiplier").asLong();
        final long minimumMultiplier =
            feeJSON.get("minFeeMultiplier").asLong();
        final long feeMultiplier = Math.max(
            medianMultiplier, minimumMultiplier);
        System.out.printf("  Fee multiplier: %d%n%n", feeMultiplier);

        // --- PLAIN TEXT MESSAGE ---
        System.out.println("==> Sending Plain Text Message");

        // Create a plain text message
        final byte[] plainMessage =
            "Hello, Symbol!".getBytes(StandardCharsets.UTF_8);
        System.out.printf("Plain message: %s%n",
            new String(plainMessage, StandardCharsets.UTF_8));

        // Build transfer transaction with plain message
        final Transaction plainTransaction =
            facade.createTransactionFromTypedDescriptor(
                new TransferTransactionV1Descriptor(
                    recipientAddress,
                    null,
                    plainMessage),
                senderKeyPair.getPublicKey(),
                feeMultiplier,
                2 * 60 * 60);

        // Sign and announce the transaction
        final CryptoTypes.Signature plainSignature =
            facade.signTransaction(senderKeyPair, plainTransaction);
        final String plainJsonPayload = SymbolTransactionFactory
            .attachSignature(plainTransaction, plainSignature);
        final String plainTransactionHash = facade.hashTransaction(
            plainTransaction).toString();
        System.out.printf("Transaction hash: %s%n", plainTransactionHash);

        put("/transactions", plainJsonPayload);
        System.out.println("Plain message transaction announced\n");

        // --- RECEIVING PLAIN TEXT MESSAGE ---

        System.out.println("<== Receiving Plain Text Message");

        // Wait for confirmation
        final JsonNode plainTxData = retrieveConfirmedTransaction(
            plainTransactionHash, "Plain message transaction");

        // Decode plain message from confirmed transaction
        final byte[] receivedPlainMessage = HexFormat.of().parseHex(
            plainTxData.get("transaction").get("message").asText());
        System.out.printf("Received plain message: %s%n%n",
            new String(receivedPlainMessage, StandardCharsets.UTF_8));

        // --- ENCRYPTED MESSAGE ---
        System.out.println("==> Sending Encrypted Message");

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

        // Encrypt the message using recipient's public key
        final byte[] secretMessage =
            "This is a secret message!".getBytes(StandardCharsets.UTF_8);
        final byte[] encryptedPayload = senderMessageEncoder.encode(
            recipientKeyPair.getPublicKey(), secretMessage
        );
        System.out.printf("Original message: %s%n",
            new String(secretMessage, StandardCharsets.UTF_8));
        System.out.printf("Encrypted payload: %s%n",
            HexFormat.of().formatHex(encryptedPayload));

        // Build transfer transaction with encrypted message
        final Transaction encryptedTransaction =
            facade.createTransactionFromTypedDescriptor(
                new TransferTransactionV1Descriptor(
                    recipientAddress,
                    null,
                    encryptedPayload),
                senderKeyPair.getPublicKey(),
                feeMultiplier,
                2 * 60 * 60);

        // Sign and announce the transaction
        final CryptoTypes.Signature encryptedSignature =
            facade.signTransaction(senderKeyPair, encryptedTransaction);
        final String encryptedJsonPayload = SymbolTransactionFactory
            .attachSignature(encryptedTransaction, encryptedSignature);
        final String encryptedTransactionHash = facade.hashTransaction(
            encryptedTransaction).toString();
        System.out.printf("Transaction hash: %s%n",
            encryptedTransactionHash);

        put("/transactions", encryptedJsonPayload);
        System.out.println("Encrypted message transaction announced\n");

        // --- RECEIVING ENCRYPTED MESSAGE ---
        System.out.println("<== Receiving Encrypted Message");

        // Wait for confirmation
        final JsonNode encryptedTxData = retrieveConfirmedTransaction(
            encryptedTransactionHash, "Encrypted message transaction");

        // Decode encrypted message using recipient's private key
        final MessageEncoder recipientMessageEncoder = new MessageEncoder(
            recipientKeyPair);
        final byte[] receivedEncryptedMessage = HexFormat.of().parseHex(
            encryptedTxData.get("transaction").get("message").asText());

        // Get sender's public key from the transaction
        final CryptoTypes.PublicKey senderPublicKeyFromTx =
            new CryptoTypes.PublicKey(encryptedTxData.get("transaction")
                .get("signerPublicKey").asText());

        final MessageEncoderResult result =
            recipientMessageEncoder.tryDecode(
                senderPublicKeyFromTx, receivedEncryptedMessage);

        if (result.isDecoded()) {
            System.out.printf("Recipient decrypted message: %s%n",
                new String((byte[]) result.message(),
                    StandardCharsets.UTF_8));
        } else {
            System.out.println("Recipient failed to decrypt message");
        }
    }

    private void put(
        final String path,
        final String payload
    ) throws IOException, InterruptedException {
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(nodeUrl + path))
            .header("Content-Type", "application/json")
            .PUT(HttpRequest.BodyPublishers.ofString(payload))
            .build();
        HTTP_CLIENT.send(request, BodyHandlers.ofString());
    }
}

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');
        // Set up sender and recipient accounts
        final String senderPrivateKeyString = System.getenv().getOrDefault(
            "SENDER_PRIVATE_KEY", "0".repeat(64));
        final KeyPair senderKeyPair = new KeyPair(
            new CryptoTypes.PrivateKey(senderPrivateKeyString));
        final Address senderAddress = facade.network.publicKeyToAddress(
            senderKeyPair.getPublicKey());

        final String recipientPrivateKeyString = System.getenv()
            .getOrDefault("RECIPIENT_PRIVATE_KEY", "1".repeat(64));
        final KeyPair recipientKeyPair = new KeyPair(
            new CryptoTypes.PrivateKey(recipientPrivateKeyString));
        final Address recipientAddress = facade.network.publicKeyToAddress(
            recipientKeyPair.getPublicKey());

        System.out.printf("Sender address: %s%n", senderAddress);
        System.out.printf("Recipient address: %s%n%n", recipientAddress);

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

このチュートリアルでは、プレーンテキストおよび暗号化されたメッセージの送受信の両方を実演するために、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.create_transaction_from_descriptor(
    {
        "type": "transfer_transaction_v1",
        "recipient_address": recipient_address,
        "mosaics": [],
        "message": plain_message,
    },
    sender_key_pair.public_key,
    fee_multiplier,
    2 * 60 * 60)
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.createTransactionFromTypedDescriptor(
    new descriptors.TransferTransactionV1Descriptor(
        recipientAddress,
        [],
        plainMessage
    ),
    senderKeyPair.publicKey,
    feeMultiplier,
    2 * 60 * 60);
        System.out.println("==> Sending Plain Text Message");

        // Create a plain text message
        final byte[] plainMessage =
            "Hello, Symbol!".getBytes(StandardCharsets.UTF_8);
        System.out.printf("Plain message: %s%n",
            new String(plainMessage, StandardCharsets.UTF_8));

        // Build transfer transaction with plain message
        final Transaction plainTransaction =
            facade.createTransactionFromTypedDescriptor(
                new TransferTransactionV1Descriptor(
                    recipientAddress,
                    null,
                    plainMessage),
                senderKeyPair.getPublicKey(),
                feeMultiplier,
                2 * 60 * 60);

トランザクション記述子に 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');
        System.out.println("<== Receiving Plain Text Message");

        // Wait for confirmation
        final JsonNode plainTxData = retrieveConfirmedTransaction(
            plainTransactionHash, "Plain message transaction");

        // Decode plain message from confirmed transaction
        final byte[] receivedPlainMessage = HexFormat.of().parseHex(
            plainTxData.get("transaction").get("message").asText());
        System.out.printf("Received plain message: %s%n%n",
            new String(receivedPlainMessage, StandardCharsets.UTF_8));

トランザクションをアナウンスした後、 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(f"Encrypted payload: {hexlify(encrypted_payload).decode("utf-8")}")

# Build transfer transaction with encrypted message
encrypted_transaction = facade.create_transaction_from_descriptor(
    {
        "type": "transfer_transaction_v1",
        "recipient_address": recipient_address,
        "mosaics": [],
        "message": encrypted_payload,
    },
    sender_key_pair.public_key,
    fee_multiplier,
    2 * 60 * 60)
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));
console.log('Encrypted payload:',
    Buffer.from(encryptedPayload).toString('hex'));

// Build transfer transaction with encrypted message
const encryptedTransaction = facade.createTransactionFromTypedDescriptor(
    new descriptors.TransferTransactionV1Descriptor(
        recipientAddress,
        [],
        encryptedPayload
    ),
    senderKeyPair.publicKey,
    feeMultiplier,
    2 * 60 * 60);
        System.out.println("==> Sending Encrypted Message");

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

        // Encrypt the message using recipient's public key
        final byte[] secretMessage =
            "This is a secret message!".getBytes(StandardCharsets.UTF_8);
        final byte[] encryptedPayload = senderMessageEncoder.encode(
            recipientKeyPair.getPublicKey(), secretMessage
        );
        System.out.printf("Original message: %s%n",
            new String(secretMessage, StandardCharsets.UTF_8));
        System.out.printf("Encrypted payload: %s%n",
            HexFormat.of().formatHex(encryptedPayload));

        // Build transfer transaction with encrypted message
        final Transaction encryptedTransaction =
            facade.createTransactionFromTypedDescriptor(
                new TransferTransactionV1Descriptor(
                    recipientAddress,
                    null,
                    encryptedPayload),
                senderKeyPair.getPublicKey(),
                feeMultiplier,
                2 * 60 * 60);

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

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

  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("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');
}
        System.out.println("<== Receiving Encrypted Message");

        // Wait for confirmation
        final JsonNode encryptedTxData = retrieveConfirmedTransaction(
            encryptedTransactionHash, "Encrypted message transaction");

        // Decode encrypted message using recipient's private key
        final MessageEncoder recipientMessageEncoder = new MessageEncoder(
            recipientKeyPair);
        final byte[] receivedEncryptedMessage = HexFormat.of().parseHex(
            encryptedTxData.get("transaction").get("message").asText());

        // Get sender's public key from the transaction
        final CryptoTypes.PublicKey senderPublicKeyFromTx =
            new CryptoTypes.PublicKey(encryptedTxData.get("transaction")
                .get("signerPublicKey").asText());

        final MessageEncoderResult result =
            recipientMessageEncoder.tryDecode(
                senderPublicKeyFromTx, receivedEncryptedMessage);

        if (result.isDecoded()) {
            System.out.printf("Recipient decrypted message: %s%n",
                new String((byte[]) result.message(),
                    StandardCharsets.UTF_8));
        } else {
            System.out.println("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 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!

出力のハイライトをいくつか紹介します。

  • 平文メッセージ(9行目): 最初のトランザクションに添付されたメッセージ。暗号化されていないため、ブロックチェーンを閲覧できる誰もが読むことができます。

  • 受信した平文メッセージ(16行目): 承認されたトランザクションから、16 進数のペイロードを UTF-8 に変換して復元された同じメッセージ。

  • 元のメッセージ(19行目): 暗号化前の秘密のメッセージ。

  • 暗号化されたペイロード(20行目): 前のメッセージを で暗号化した結果で、16 進数文字列として表示されます。これがブロックチェーンに保存されます。

  • 受信者が復号したメッセージ(27行目): 承認されたトランザクションから取得し、受信者の秘密鍵と送信者の公開鍵を使って で復号した元のメッセージ。

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

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

結論⚓︎

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

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