コンテンツにスキップ

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

中級

転送トランザクション には、最大 1024 バイトのデータをトランザクションに付加できるオプションのメッセージフィールドを含められます。 メッセージは平文で送信することも、受取人の公開鍵で暗号化することもできます。暗号化すれば、意図した受取人だけが読めます。

このチュートリアルでは、平文メッセージと暗号化メッセージの両方を送信し、受信したメッセージをデコードする方法を説明します。

前提条件⚓︎

始める前に、次の準備をしてください。

さらに、手数料の計算、ネットワーク時刻、トランザクションの承認の仕組みを理解するため、転送トランザクション チュートリアルを確認してください。

完全なコード⚓︎

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.NemFacade import NemFacade
from symbolchain.nc import Amount, Message, MessageType
from symbolchain.nem.FeeCalculator import calculate_transaction_fee
from symbolchain.nem.MessageEncoder import MessageEncoder
from symbolchain.nem.Network import NetworkTimestamp

# Configuration
NODE_URL = os.getenv('NODE_URL', 'http://libertalia.nemtest.net:7890')
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 = 120

    while attempts < max_attempts:
        try:
            url = f'{NODE_URL}/transaction/get?hash={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 = NemFacade('testnet')

sender_private_key_string = os.getenv(
    'SENDER_PRIVATE_KEY',
    '0000000000000000000000000000000000000000000000000000000000000000',
)
sender_key_pair = NemFacade.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 = NemFacade.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 = '/time-sync/network-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())
    network_time = response_json['receiveTimeStamp'] // 1000
    timestamp = NetworkTimestamp(network_time)
    deadline = timestamp.add_hours(2)
    print(f'  Network time: {network_time} s since the nemesis block\n')

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

# Create a plain text message
plain_message = 'Hello, NEM!'.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_v2',
        'signer_public_key': sender_key_pair.public_key,
        'timestamp': timestamp.timestamp,
        'deadline': deadline.timestamp,
        'recipient_address': recipient_address,
        'amount': 0,
        'message': {
            'message_type': 'plain',
            'message': plain_message,
        },
    }
)
plain_transaction.fee = Amount(
    calculate_transaction_fee(plain_transaction))

# 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}/transaction/announce',
    data=plain_json_payload.encode('utf-8'),
    headers={'Content-Type': 'application/json'},
    method='POST',
)
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']['payload']
)
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_message = sender_message_encoder.encode(
    recipient_key_pair.public_key, secret_message
)
print(f'Original message: {secret_message.decode("utf-8")}')
encrypted_payload = hexlify(encrypted_message.message).decode('utf-8')
print(f'Encrypted payload: {encrypted_payload}')

# Build transfer transaction with encrypted message
encrypted_transaction = facade.transaction_factory.create(
    {
        'type': 'transfer_transaction_v2',
        'signer_public_key': sender_key_pair.public_key,
        'timestamp': timestamp.timestamp,
        'deadline': deadline.timestamp,
        'recipient_address': recipient_address,
        'amount': 0,
        'message': {
            'message_type': 'encrypted',
            'message': encrypted_message.message,
        },
    }
)
encrypted_transaction.fee = Amount(
    calculate_transaction_fee(encrypted_transaction))

# 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}/transaction/announce',
    data=encrypted_json_payload.encode('utf-8'),
    headers={'Content-Type': 'application/json'},
    method='POST',
)
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 = Message()
received_encrypted_message.message_type = MessageType.ENCRYPTED
received_encrypted_message.message = bytes.fromhex(
    encrypted_tx_data['transaction']['message']['payload']
)

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

(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,
    NemFacade,
    NetworkTimestamp,
    calculateTransactionFee,
    models
} from 'symbol-sdk/nem';

// Configuration
const NODE_URL = process.env.NODE_URL ||
    'http://libertalia.nemtest.net:7890';
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 = 120;

    while (attempts < maxAttempts) {
        const response = await fetch(
            `${NODE_URL}/transaction/get?hash=${hash}`);
        if (response.ok) {
            console.log(`  ${label} confirmed!`);
            return response.json();
        }
        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 NemFacade('testnet');

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

const recipientPrivateKeyString = process.env.RECIPIENT_PRIVATE_KEY ||
    '1111111111111111111111111111111111111111111111111111111111111111';
const recipientKeyPair = new NemFacade.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 = '/time-sync/network-time';
console.log('Fetching current network time from', timePath);
const timeResponse = await fetch(`${NODE_URL}${timePath}`);
const timeJSON = await timeResponse.json();
const networkTime = Math.floor(timeJSON.receiveTimeStamp / 1000);
const timestamp = new NetworkTimestamp(networkTime);
const deadline = timestamp.addHours(2);
console.log('  Network time:', networkTime,
    's since the nemesis block', '\n');

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

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

// Build transfer transaction with plain message
const plainTransaction = facade.transactionFactory.create({
    type: 'transfer_transaction_v2',
    signerPublicKey: senderKeyPair.publicKey.toString(),
    timestamp: timestamp.timestamp,
    deadline: deadline.timestamp,
    recipientAddress: recipientAddress.toString(),
    amount: 0n,
    message: {
        messageType: 'plain',
        message: plainMessage
    }
});
plainTransaction.fee = new models.Amount(
    calculateTransactionFee(plainTransaction));

// 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}/transaction/announce`, {
    method: 'POST',
    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.payload, '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 encryptedMessage = senderMessageEncoder.encode(
    recipientKeyPair.publicKey, secretMessage
);
console.log('Original message:', new TextDecoder().decode(secretMessage));
console.log('Encrypted payload:',
    Buffer.from(encryptedMessage.message).toString('hex'));

// Build transfer transaction with encrypted message
const encryptedTransaction = facade.transactionFactory.create({
    type: 'transfer_transaction_v2',
    signerPublicKey: senderKeyPair.publicKey.toString(),
    timestamp: timestamp.timestamp,
    deadline: deadline.timestamp,
    recipientAddress: recipientAddress.toString(),
    amount: 0n,
    message: {
        messageType: 'encrypted',
        message: encryptedMessage.message
    }
});
encryptedTransaction.fee = new models.Amount(
    calculateTransactionFee(encryptedTransaction));

// 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}/transaction/announce`, {
    method: 'POST',
    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 = new models.Message();
receivedEncryptedMessage.messageType = models.MessageType.ENCRYPTED;
receivedEncryptedMessage.message = Buffer.from(
    encryptedTxData.transaction.message.payload, 'hex');

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

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 = NemFacade('testnet')

sender_private_key_string = os.getenv(
    'SENDER_PRIVATE_KEY',
    '0000000000000000000000000000000000000000000000000000000000000000',
)
sender_key_pair = NemFacade.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 = NemFacade.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 NemFacade('testnet');

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

const recipientPrivateKeyString = process.env.RECIPIENT_PRIVATE_KEY ||
    '1111111111111111111111111111111111111111111111111111111111111111';
const recipientKeyPair = new NemFacade.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_KEYRECIPIENT_PRIVATE_KEY 環境変数からそれぞれの秘密鍵を読み込み、設定されていない場合はテスト用キーをデフォルト値として使用します。 受取人の公開鍵とアドレスは、秘密鍵から導出されます。

公開鍵を取得する

アドレスだけがわかっている場合は、/account/get GET エンドポイントを使ってネットワークから公開鍵を取得できます。 アカウントの公開鍵が利用可能になるのは、少なくとも 1 回トランザクションを送信した後です。

平文メッセージを送信する⚓︎

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

# Create a plain text message
plain_message = 'Hello, NEM!'.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_v2',
        'signer_public_key': sender_key_pair.public_key,
        'timestamp': timestamp.timestamp,
        'deadline': deadline.timestamp,
        'recipient_address': recipient_address,
        'amount': 0,
        'message': {
            'message_type': 'plain',
            'message': plain_message,
        },
    }
)
console.log('==> Sending Plain Text Message');

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

// Build transfer transaction with plain message
const plainTransaction = facade.transactionFactory.create({
    type: 'transfer_transaction_v2',
    signerPublicKey: senderKeyPair.publicKey.toString(),
    timestamp: timestamp.timestamp,
    deadline: deadline.timestamp,
    recipientAddress: recipientAddress.toString(),
    amount: 0n,
    message: {
        messageType: 'plain',
        message: plainMessage
    }
});

トランザクションディスクリプタに mosaics フィールドと message フィールドの両方を含めることで、モザイクの転送とメッセージを組み合わせられます。

その後、転送トランザクションで XEM を送信する と同じ手順でトランザクションに署名してアナウンスします。

メッセージの制約:

  • 最大サイズ: 1024 バイト(ネットワークはそれより大きいメッセージを拒否します)。
  • エンコーディング: 慣例では UTF-8 ですが、プロトコルは標準を強制しません。
  • プライバシー: 暗号化しない限り、すべてのメッセージはブロックチェーン上で公開されます。

大きなデータを扱う

1024 バイトを超えるデータが必要なアプリケーションでは、一般的に次の方法を使います。

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

平文メッセージを受信する⚓︎

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']['payload']
)
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.payload, 'hex');
console.log('Received plain message:',
    new TextDecoder().decode(receivedPlainMessage), '\n');

トランザクションをアナウンスした後、 ヘルパー関数はトランザクションが承認されるまで /transaction/get 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_message = sender_message_encoder.encode(
    recipient_key_pair.public_key, secret_message
)
print(f'Original message: {secret_message.decode("utf-8")}')
encrypted_payload = hexlify(encrypted_message.message).decode('utf-8')
print(f'Encrypted payload: {encrypted_payload}')

# Build transfer transaction with encrypted message
encrypted_transaction = facade.transaction_factory.create(
    {
        'type': 'transfer_transaction_v2',
        'signer_public_key': sender_key_pair.public_key,
        'timestamp': timestamp.timestamp,
        'deadline': deadline.timestamp,
        'recipient_address': recipient_address,
        'amount': 0,
        'message': {
            'message_type': 'encrypted',
            'message': encrypted_message.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 encryptedMessage = senderMessageEncoder.encode(
    recipientKeyPair.publicKey, secretMessage
);
console.log('Original message:', new TextDecoder().decode(secretMessage));
console.log('Encrypted payload:',
    Buffer.from(encryptedMessage.message).toString('hex'));

// Build transfer transaction with encrypted message
const encryptedTransaction = facade.transactionFactory.create({
    type: 'transfer_transaction_v2',
    signerPublicKey: senderKeyPair.publicKey.toString(),
    timestamp: timestamp.timestamp,
    deadline: deadline.timestamp,
    recipientAddress: recipientAddress.toString(),
    amount: 0n,
    message: {
        messageType: 'encrypted',
        message: encryptedMessage.message
    }
});

暗号化メッセージは、送信者の秘密鍵と受取人の公開鍵から導出した共有秘密を使ってメッセージの内容を保護し、機密性を提供します。 送信者と受取人は、自分の秘密鍵と相手の公開鍵を使ってメッセージを復号できます。

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

  1. 送信者のキーペアで を作成する。
  2. 受取人の公開鍵とメッセージバイトを使い、 でメッセージをエンコードする。
  3. 暗号化したペイロードをトランザクションの message フィールドに付加する。

その後、転送トランザクションで XEM を送信する と同じ手順でトランザクションに署名してアナウンスします。

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

NEM プロトコルは、メッセージ暗号化の標準を定義していません。 送信者と受取人は、メッセージを暗号化するかどうか、および使用する暗号方式について事前に合意する必要があります。

AES-GCM を実装します。これは、ほとんどのウォレットとアプリケーションが使用する慣例です。 このクラスには、初期の NEM ウォレットとアプリケーションが使用した従来の AES-CBC メッセージと互換性を保つため、 もあります。 は両方の方式を自動的に検出してデコードします。

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

暗号化メッセージを受信する⚓︎

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 = Message()
received_encrypted_message.message_type = MessageType.ENCRYPTED
received_encrypted_message.message = bytes.fromhex(
    encrypted_tx_data['transaction']['message']['payload']
)

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

(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 = new models.Message();
receivedEncryptedMessage.messageType = models.MessageType.ENCRYPTED;
receivedEncryptedMessage.message = Buffer.from(
    encryptedTxData.transaction.message.payload, 'hex');

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

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');
}

暗号化メッセージのトランザクションをアナウンスした後、 ヘルパー関数は承認を待ちます。

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

メソッドは、復号に成功したかどうかを示す タプルを返します。成功した場合は元の平文バイトが含まれ、さらにデコードする必要があります。

復号はどちらの方向でも機能します

暗号化では両方のキーペアから導出した共有秘密を使うため、送信者も自分の秘密鍵と受取人の公開鍵でメッセージを復号できます。 これにより、ブロックチェーン上に公開された後で、双方がメッセージの内容を検証できます。

復号に失敗する原因には、次のものがあります。

  • メッセージが別の受取人向けに暗号化されている。
  • メッセージが破損している、または改ざんされている。
  • メッセージが暗号化されておらず、平文である。
  • 相手に対して誤った公開鍵を使った。
  • 暗号化と復号で同じ慣例を使っていない。

出力⚓︎

以下は、プログラムの実行時の出力例です。

Using node http://libertalia.nemtest.net:7890
Sender address: TBONKWCOWBZYZB2I5JD3LSDBQVBYHB757VN3SKPP
Recipient address: TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4

Fetching current network time from /time-sync/network-time
  Network time: 355248540 s since the nemesis block

==> Sending Plain Text Message
Plain message: Hello, NEM!
Transaction hash: 2F8F20CAF8F6FA42ADE05ED85FED0B4D1DA88051972405C4AE2D181B01FB69C3
Plain message transaction announced

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

==> Sending Encrypted Message
Original message: This is a secret message!
Encrypted payload: 3fefcf6e4f1e5e2165f941a5c15ea66778f82eded50cfbde5fc27eba24f4580ff72fb9d0b45061747be0324a120dc357dd11351c09
Transaction hash: 76604471D5A345E6F5CE20C65D618BC6F9A600F1DF515A696B2152E0E2D0B427
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 行目): 1 つ目のトランザクションに付加されたメッセージ。暗号化されていないため、ブロックチェーンを調べる人は誰でも読めます。
  • 受信した平文メッセージ(16 行目): 16 進数ペイロードを UTF-8 に戻し、承認されたトランザクションから復元した同じメッセージ。
  • 元のメッセージ(19 行目): 暗号化前の秘密メッセージ。
  • 暗号化ペイロード(20 行目): で前のメッセージを暗号化した結果で、16 進数文字列として表示されます。これがブロックチェーンに保存される内容です。
  • 受取人が復号したメッセージ(27 行目): 承認されたトランザクションから取得し、受取人の秘密鍵と送信者の公開鍵を使って で復号した元のメッセージ。

出力に表示されたトランザクションハッシュを NEM テストネットエクスプローラー で検索すれば、トランザクションを確認できます。 エクスプローラーは秘密鍵にアクセスできないため、暗号化メッセージを復号できません。

まとめ⚓︎

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

手順 関連ドキュメント
テキストを UTF-8 バイトに変換する TextEncoder(JS)と str.encode/bytes.decode(Python)
SDK の一部ではないシステムメソッド
メッセージを暗号化する
メッセージを復号する