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

モザイク定義の証明⚓︎

Symbol のすべての ブロック ヘッダーには、すべての モザイク 定義を含むチェーン状態全体をカバーする stateHash があります。 証明(Proof)を要求し、ローカルで検証することで、ノードを信頼したり自分でノードを運用したりすることなく、ノードが返すデータが実際にチェーンに記録されているものと一致することを確認できます。

このチュートリアルでは、API からモザイク定義を取得し、チェーンで使用されているのと同じバイナリ形式にシリアライズし、 パトリシアツリー の証明を使用してブロックの stateHash と照合して結果を検証する方法を説明します。

前提条件⚓︎

開始する前に、 開発環境をセットアップ してください。 このチュートリアルはネットワークからデータを読み取るだけです。 アカウントXYM の残高は必要ありません。

さらに、 ブロックハッシュ (特にステートハッシュのセクション)の仕組みを確認してください。

完全なコード⚓︎

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

import hashlib
import json
import os
import urllib.request
from binascii import unhexlify

from symbolchain.BufferWriter import BufferWriter
from symbolchain.CryptoTypes import Hash256
from symbolchain.symbol.Merkle import (
    deserialize_patricia_tree_nodes,
    prove_patricia_merkle)

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

try:
    # Fetch the network currency mosaic ID
    url = f'{NODE_URL}/network/properties'
    with urllib.request.urlopen(url) as response:
        props = json.loads(response.read().decode())
    raw_id = props['chain']['currencyMosaicId']
    mosaic_id = int(raw_id.replace("'", ""), 16)
    mosaic_id_hex = f'{mosaic_id:016X}'
    print(f'Currency mosaic ID: {mosaic_id_hex}')

    # Fetch the mosaic properties
    mosaic_path = f'/mosaics/{mosaic_id_hex}'
    print(f'Fetching mosaic from {mosaic_path}')
    url = f'{NODE_URL}{mosaic_path}'
    with urllib.request.urlopen(url) as response:
        mosaic_data = json.loads(response.read().decode())
    mosaic = mosaic_data['mosaic']
    print(json.dumps(mosaic, indent=2))

    # Serialize and hash the mosaic properties
    writer = BufferWriter()
    writer.write_int(int(mosaic['version']), 2)
    writer.write_int(int(mosaic['id'], 16), 8)
    writer.write_int(int(mosaic['supply']), 8)
    writer.write_int(int(mosaic['startHeight']), 8)
    writer.write_bytes(unhexlify(mosaic['ownerAddress']))
    writer.write_int(int(mosaic['revision']), 4)
    writer.write_int(int(mosaic['flags']), 1)
    writer.write_int(int(mosaic['divisibility']), 1)
    writer.write_int(int(mosaic['duration']), 8)
    hashed_value = Hash256(hashlib.sha3_256(writer.buffer).digest())
    print(f'Hashed value: {hashed_value}')

    # Hash the mosaic ID to get the encoded key
    writer = BufferWriter()
    writer.write_int(int(mosaic['id'], 16), 8)
    encoded_key = Hash256(hashlib.sha3_256(writer.buffer).digest())
    print(f'Encoded key: {encoded_key}')

    # Fetch the current network height
    url = f'{NODE_URL}/chain/info'
    with urllib.request.urlopen(url) as response:
        chain_info = json.loads(response.read().decode())
    height = int(chain_info['height'])
    print(f'Current height: {height}')

    # Fetch the block's state hash and roots
    block_path = f'/blocks/{height}'
    print(f'Fetching block from {block_path}')
    url = f'{NODE_URL}{block_path}'
    with urllib.request.urlopen(url) as response:
        block_data = json.loads(response.read().decode())
    state_hash = Hash256(block_data['block']['stateHash'])
    sub_cache_key = 'stateHashSubCacheMerkleRoots'
    roots = [Hash256(r) for r in block_data['meta'][sub_cache_key]]
    print(f'State hash: {state_hash}')

    # Fetch the patricia tree path
    tree_url = f'/mosaics/{mosaic_id_hex}/merkle'
    print(f'Fetching tree path from {tree_url}')
    url = f'{NODE_URL}{tree_url}'
    with urllib.request.urlopen(url) as response:
        tree_data = json.loads(response.read().decode())
    merkle_path = deserialize_patricia_tree_nodes(
        unhexlify(tree_data['raw']))
    print(f'Tree path: {len(merkle_path)} nodes')
    key_hex = str(encoded_key)
    key_pos = 0
    for i, node in enumerate(merkle_path):
        key_pos += node.path.size
        path_str = (f'  path: {node.hex_path}' if node.path.size else '')
        if hasattr(node, 'value'):
            print(f'  [{i}] leaf{path_str}  value: {node.value}')
        else:
            nibble = key_hex[key_pos]
            key_pos += 1
            active = [
                f'{j:X}' for j, link in enumerate(node.links) if link
            ]
            print(f'  [{i}] branch{path_str}'
                f'  links: [{",".join(active)}]'
                f'  -> follow {nibble}')

    # Verify the mosaic state
    result = prove_patricia_merkle(
        encoded_key, hashed_value, merkle_path, state_hash, roots)

    if result.name == 'VALID_POSITIVE':
        print(f'Mosaic {mosaic_id_hex} state verified at height {height}')
    else:
        raise RuntimeError(
            f'Mosaic {mosaic_id_hex} proof failed: {result.name}')

except Exception as e:
    print(e)

Download source

import crypto from 'crypto';
import { Hash256, utils } from 'symbol-sdk';
import {
    deserializePatriciaTreeNodes,
    provePatriciaMerkle
} from 'symbol-sdk/symbol';

const { hexToUint8, intToBytes } = utils;

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

const sha3_256 = (data) =>
    crypto.createHash('sha3-256').update(data).digest();

const concat = (...arrays) => {
    const total = arrays.reduce((n, a) => n + a.length, 0);
    const buf = new Uint8Array(total);
    let off = 0;
    for (const a of arrays) {
        buf.set(a, off);
        off += a.length;
    }
    return buf;
};

try {
    // Fetch the network currency mosaic ID
    const propsRes = await fetch(`${NODE_URL}/network/properties`);
    const props = await propsRes.json();
    const rawId = props.chain.currencyMosaicId;
    const mosaicId = BigInt(rawId.replaceAll("'", ''));
    const mosaicIdHex = mosaicId.toString(16)
        .toUpperCase().padStart(16, '0');
    console.log('Currency mosaic ID:', mosaicIdHex);

    // Fetch the mosaic properties
    const mosaicPath = `/mosaics/${mosaicIdHex}`;
    console.log('Fetching mosaic from', mosaicPath);
    const mosaicRes = await fetch(`${NODE_URL}${mosaicPath}`);
    const mosaicData = await mosaicRes.json();
    const mosaic = mosaicData.mosaic;
    console.log(JSON.stringify(mosaic, undefined, 2));

    // Serialize and hash the mosaic properties
    const serialized = concat(
        intToBytes(parseInt(mosaic.version), 2),
        intToBytes(BigInt(`0x${mosaic.id}`), 8),
        intToBytes(BigInt(mosaic.supply), 8),
        intToBytes(BigInt(mosaic.startHeight), 8),
        hexToUint8(mosaic.ownerAddress),
        intToBytes(parseInt(mosaic.revision), 4),
        intToBytes(parseInt(mosaic.flags), 1),
        intToBytes(parseInt(mosaic.divisibility), 1),
        intToBytes(BigInt(mosaic.duration), 8));
    const hashedValue = new Hash256(sha3_256(serialized));
    console.log('Hashed value:', hashedValue.toString());

    // Hash the mosaic ID to get the encoded key
    const keyBytes = intToBytes(BigInt(`0x${mosaic.id}`), 8);
    const encodedKey = new Hash256(sha3_256(keyBytes));
    console.log('Encoded key:', encodedKey.toString());

    // Fetch the current network height
    const chainRes = await fetch(`${NODE_URL}/chain/info`);
    const chainInfo = await chainRes.json();
    const height = parseInt(chainInfo.height, 10);
    console.log('Current height:', height);

    // Fetch the block's state hash and roots
    const blockPath = `/blocks/${height}`;
    console.log('Fetching block from', blockPath);
    const blockRes = await fetch(`${NODE_URL}${blockPath}`);
    const blockData = await blockRes.json();
    const stateHash = new Hash256(blockData.block.stateHash);
    const roots = blockData.meta.stateHashSubCacheMerkleRoots
        .map(r => new Hash256(r));
    console.log('State hash:', stateHash.toString());

    // Fetch the patricia tree path
    const treeUrl = `/mosaics/${mosaicIdHex}/merkle`;
    console.log('Fetching tree path from', treeUrl);
    const treeRes = await fetch(
        `${NODE_URL}${treeUrl}`);
    const treeData = await treeRes.json();
    const merklePath =
        deserializePatriciaTreeNodes(hexToUint8(treeData.raw));
    console.log('Tree path:', merklePath.length, 'nodes');
    const keyHex = encodedKey.toString();
    let keyPos = 0;
    merklePath.forEach((node, i) => {
        keyPos += node.path.size;
        const pathStr = node.path.size
            ? `  path: ${node.hexPath}` : '';
        if ('value' in node) {
            console.log(`  [${i}] leaf${pathStr}  value: ${node.value}`);
        } else {
            const nibble = keyHex[keyPos];
            keyPos += 1;
            const active = node.links
                .map((l, j) => (l ? j.toString(16).toUpperCase() : null))
                .filter(x => x !== null);
            console.log(
                `  [${i}] branch${pathStr}`
                + `  links: [${active}]`
                + `  -> follow ${nibble}`);
        }
    });

    // Verify the mosaic state
    const result = provePatriciaMerkle(
        encodedKey, hashedValue, merklePath, stateHash, roots);

    if (result === 0x0001) { // VALID_POSITIVE
        console.log(
            `Mosaic ${mosaicIdHex} state verified at height ${height}`);
    } else {
        throw new Error(
            `Mosaic ${mosaicIdHex} proof failed: ${result}`);
    }
} catch (e) {
    console.error(e.message);
}

Download source

この例では、 /network/properties GET から自動的に検出された ID を持つ、ネットワークの通貨モザイク(メインネット では XYM )を検証します。 通貨モザイクはすべての Symbol ネットワークに存在するため便利な選択肢ですが、ID を置き換えればどのモザイクでも同じプロセスが機能します。

コード解説⚓︎

モザイク定義の取得⚓︎

    # Fetch the network currency mosaic ID
    url = f'{NODE_URL}/network/properties'
    with urllib.request.urlopen(url) as response:
        props = json.loads(response.read().decode())
    raw_id = props['chain']['currencyMosaicId']
    mosaic_id = int(raw_id.replace("'", ""), 16)
    mosaic_id_hex = f'{mosaic_id:016X}'
    print(f'Currency mosaic ID: {mosaic_id_hex}')

    # Fetch the mosaic properties
    mosaic_path = f'/mosaics/{mosaic_id_hex}'
    print(f'Fetching mosaic from {mosaic_path}')
    url = f'{NODE_URL}{mosaic_path}'
    with urllib.request.urlopen(url) as response:
        mosaic_data = json.loads(response.read().decode())
    mosaic = mosaic_data['mosaic']
    print(json.dumps(mosaic, indent=2))
    // Fetch the network currency mosaic ID
    const propsRes = await fetch(`${NODE_URL}/network/properties`);
    const props = await propsRes.json();
    const rawId = props.chain.currencyMosaicId;
    const mosaicId = BigInt(rawId.replaceAll("'", ''));
    const mosaicIdHex = mosaicId.toString(16)
        .toUpperCase().padStart(16, '0');
    console.log('Currency mosaic ID:', mosaicIdHex);

    // Fetch the mosaic properties
    const mosaicPath = `/mosaics/${mosaicIdHex}`;
    console.log('Fetching mosaic from', mosaicPath);
    const mosaicRes = await fetch(`${NODE_URL}${mosaicPath}`);
    const mosaicData = await mosaicRes.json();
    const mosaic = mosaicData.mosaic;
    console.log(JSON.stringify(mosaic, undefined, 2));

コードはまず、 /network/properties GET からネットワーク通貨モザイク ID を取得します。 currencyMosaicId フィールドは、アポストロフィが埋め込まれた16進文字列(例: 0x72C0212E'67A08BCE )であるため、コードはパースする前にそれらを取り除きます。

その後、モザイクの完全な定義が /mosaics/{mosaicId} GET から取得されます。 レスポンスには、モザイクの定義を構成するすべてのフィールド( versionidsupplystartHeightownerAddressrevisionflagsdivisibilityduration )が含まれています。

キーと値のハッシュの計算⚓︎

    # Serialize and hash the mosaic properties
    writer = BufferWriter()
    writer.write_int(int(mosaic['version']), 2)
    writer.write_int(int(mosaic['id'], 16), 8)
    writer.write_int(int(mosaic['supply']), 8)
    writer.write_int(int(mosaic['startHeight']), 8)
    writer.write_bytes(unhexlify(mosaic['ownerAddress']))
    writer.write_int(int(mosaic['revision']), 4)
    writer.write_int(int(mosaic['flags']), 1)
    writer.write_int(int(mosaic['divisibility']), 1)
    writer.write_int(int(mosaic['duration']), 8)
    hashed_value = Hash256(hashlib.sha3_256(writer.buffer).digest())
    print(f'Hashed value: {hashed_value}')

    # Hash the mosaic ID to get the encoded key
    writer = BufferWriter()
    writer.write_int(int(mosaic['id'], 16), 8)
    encoded_key = Hash256(hashlib.sha3_256(writer.buffer).digest())
    print(f'Encoded key: {encoded_key}')
    // Serialize and hash the mosaic properties
    const serialized = concat(
        intToBytes(parseInt(mosaic.version), 2),
        intToBytes(BigInt(`0x${mosaic.id}`), 8),
        intToBytes(BigInt(mosaic.supply), 8),
        intToBytes(BigInt(mosaic.startHeight), 8),
        hexToUint8(mosaic.ownerAddress),
        intToBytes(parseInt(mosaic.revision), 4),
        intToBytes(parseInt(mosaic.flags), 1),
        intToBytes(parseInt(mosaic.divisibility), 1),
        intToBytes(BigInt(mosaic.duration), 8));
    const hashedValue = new Hash256(sha3_256(serialized));
    console.log('Hashed value:', hashedValue.toString());

    // Hash the mosaic ID to get the encoded key
    const keyBytes = intToBytes(BigInt(`0x${mosaic.id}`), 8);
    const encodedKey = new Hash256(sha3_256(keyBytes));
    console.log('Encoded key:', encodedKey.toString());

モザイク定義を検証するために、コードはチェーンが内部に保存している正確なハッシュを再現する必要があります。 これには、 MosaicEntry スキーマで定義されている正確なフィールドの順序とサイズで、すべてのフィールドをバイナリバッファにシリアライズし、その結果の SHA3-256ハッシュを計算する必要があります。

メモ" "ネストされた構造体

MosaicEntry には MosaicDefinition 構造体が含まれており、これにはさらに MosaicProperties 構造体が含まれています。 コードはこれら3つのレベルすべてのフィールドをシリアライズするため、完全なフィールドリストは MosaicEntry 単体で示されているものよりも長くなります。

モザイク サブキャッシュパトリシア木 は、各モザイクをキーと値のペアとしてリーフ(葉)ノードに保存します。 値(value) はハッシュ化された値(シリアライズされた定義の SHA3-256)です。 キー(key) はモザイク ID (8バイト、リトルエンディアン)のみを SHA3-256 でハッシュ化することで計算され、ツリー内のモザイクのリーフノードを見つけるために使用されます。

ブロックステートハッシュの取得⚓︎

    # Fetch the current network height
    url = f'{NODE_URL}/chain/info'
    with urllib.request.urlopen(url) as response:
        chain_info = json.loads(response.read().decode())
    height = int(chain_info['height'])
    print(f'Current height: {height}')

    # Fetch the block's state hash and roots
    block_path = f'/blocks/{height}'
    print(f'Fetching block from {block_path}')
    url = f'{NODE_URL}{block_path}'
    with urllib.request.urlopen(url) as response:
        block_data = json.loads(response.read().decode())
    state_hash = Hash256(block_data['block']['stateHash'])
    sub_cache_key = 'stateHashSubCacheMerkleRoots'
    roots = [Hash256(r) for r in block_data['meta'][sub_cache_key]]
    print(f'State hash: {state_hash}')
    // Fetch the current network height
    const chainRes = await fetch(`${NODE_URL}/chain/info`);
    const chainInfo = await chainRes.json();
    const height = parseInt(chainInfo.height, 10);
    console.log('Current height:', height);

    // Fetch the block's state hash and roots
    const blockPath = `/blocks/${height}`;
    console.log('Fetching block from', blockPath);
    const blockRes = await fetch(`${NODE_URL}${blockPath}`);
    const blockData = await blockRes.json();
    const stateHash = new Hash256(blockData.block.stateHash);
    const roots = blockData.meta.stateHashSubCacheMerkleRoots
        .map(r => new Hash256(r));
    console.log('State hash:', stateHash.toString());

コードは /chain/info GET から現在のチェーン高を取得し、それを使用して /blocks/{height} GET から対応するブロックヘッダーを取得します。

ブロックの stateHash フィールドは、連結されたすべてのサブキャッシュマークルルートの SHA3-256 ハッシュです。 stateHashSubCacheMerkleRoots 配列には、各サブキャッシュ(アカウント、モザイク、ネームスペースなど)の個々のルートハッシュが含まれています。

ツリーパスの取得⚓︎

    # Fetch the patricia tree path
    tree_url = f'/mosaics/{mosaic_id_hex}/merkle'
    print(f'Fetching tree path from {tree_url}')
    url = f'{NODE_URL}{tree_url}'
    with urllib.request.urlopen(url) as response:
        tree_data = json.loads(response.read().decode())
    merkle_path = deserialize_patricia_tree_nodes(
        unhexlify(tree_data['raw']))
    print(f'Tree path: {len(merkle_path)} nodes')
    key_hex = str(encoded_key)
    key_pos = 0
    for i, node in enumerate(merkle_path):
        key_pos += node.path.size
        path_str = (f'  path: {node.hex_path}' if node.path.size else '')
        if hasattr(node, 'value'):
            print(f'  [{i}] leaf{path_str}  value: {node.value}')
        else:
            nibble = key_hex[key_pos]
            key_pos += 1
            active = [
                f'{j:X}' for j, link in enumerate(node.links) if link
            ]
            print(f'  [{i}] branch{path_str}'
                f'  links: [{",".join(active)}]'
                f'  -> follow {nibble}')
    // Fetch the patricia tree path
    const treeUrl = `/mosaics/${mosaicIdHex}/merkle`;
    console.log('Fetching tree path from', treeUrl);
    const treeRes = await fetch(
        `${NODE_URL}${treeUrl}`);
    const treeData = await treeRes.json();
    const merklePath =
        deserializePatriciaTreeNodes(hexToUint8(treeData.raw));
    console.log('Tree path:', merklePath.length, 'nodes');
    const keyHex = encodedKey.toString();
    let keyPos = 0;
    merklePath.forEach((node, i) => {
        keyPos += node.path.size;
        const pathStr = node.path.size
            ? `  path: ${node.hexPath}` : '';
        if ('value' in node) {
            console.log(`  [${i}] leaf${pathStr}  value: ${node.value}`);
        } else {
            const nibble = keyHex[keyPos];
            keyPos += 1;
            const active = node.links
                .map((l, j) => (l ? j.toString(16).toUpperCase() : null))
                .filter(x => x !== null);
            console.log(
                `  [${i}] branch${pathStr}`
                + `  links: [${active}]`
                + `  -> follow ${nibble}`);
        }
    });

/mosaics/{mosaicId}/merkle GET エンドポイントは、モザイクサブキャッシュを通るルートから、モザイクのハッシュ値を保存するリーフまでの生のパスを返します。 deserializePatriciaTreeNodes は、この生のバイナリをツリーノードのリストに変換します。

教育目的として、コードは次にデシリアライズされたパスをたどり、確認のために各ノードを出力します。 次のステップで SDK が内部的に処理するため、このステップは検証には必要ありません。 ブランチ(枝)ノードは、そのアクティブなリンクと、次のノードに到達するためにどの ニブル をたどったかを示します。 末尾のリーフノードには、残りのパスニブルとハッシュ化された値が保存されます。

証明の検証⚓︎

    # Verify the mosaic state
    result = prove_patricia_merkle(
        encoded_key, hashed_value, merkle_path, state_hash, roots)

    if result.name == 'VALID_POSITIVE':
        print(f'Mosaic {mosaic_id_hex} state verified at height {height}')
    else:
        raise RuntimeError(
            f'Mosaic {mosaic_id_hex} proof failed: {result.name}')
    // Verify the mosaic state
    const result = provePatriciaMerkle(
        encodedKey, hashedValue, merklePath, stateHash, roots);

    if (result === 0x0001) { // VALID_POSITIVE
        console.log(
            `Mosaic ${mosaicIdHex} state verified at height ${height}`);
    } else {
        throw new Error(
            `Mosaic ${mosaicIdHex} proof failed: ${result}`);
    }

は5つの引数を受け取ります。これらはそれぞれ前のステップで計算されています。

パラメータ ソース 役割
encoded_key モザイク ID の SHA3-256 ツリー内で検索するリーフを特定する
hashed_value シリアライズされた定義の SHA3-256 リーフに保存されていると予想される値
merkle_path /mosaics/{mosaicId}/merkle GET ルートからリーフまでのブランチノードとリーフノードのチェーン
state_hash /blocks/{height} GET からの stateHash すべてのチェーン状態を含むブロックヘッダーのハッシュ
roots /blocks/{height} GET からの stateHashSubCacheMerkleRoots 各サブキャッシュの個々のルートハッシュ

その後、関数は3つの段階で証明を検証します。

  1. ブロックへのリンク: SHA3-256(roots)state_hash と一致することを確認し、サブキャッシュのルートが本物であることを確認します。 次に、 merkle_path の最初のノードのハッシュがこれらのルートのいずれか(モザイクサブキャッシュ)と一致することを確認します。
  2. ツリーをたどる: merkle_path に従ってルートからリーフまで進み、各ノードのハッシュがその親の16個のリンク(ニブル値 0F ごとに1つ)の中に現れることを確認します。
  3. リーフの一致: リーフの値が hashed_value と一致し、ツリーを通るパスが encoded_key を再構成することを確認します。

すべてのチェックに合格すると、結果は 0x0001VALID_POSITIVE )となり、API によって返されたモザイク定義が、指定された高さでチェーンに記録されているものと正確に一致することが確認されます。

結果コードの完全なセットについては、 PatriciaMerkleProofResult を参照してください。

高さの一貫性

モザイク定義、ブロックヘッダー、およびツリーパスは、すべて同じチェーン状態を反映している必要があります。 リクエストの間に新しいブロックが承認された場合、ステートハッシュが変更され、証明は失敗します。 これが発生した場合は、3つのデータをすべて再取得してやり直してください。 再試行しても証明に失敗する場合、ノードが誤ったデータを提供している可能性があります。

出力⚓︎

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

Using node https://reference.symboltest.net:3001
Currency mosaic ID: 72C0212E67A08BCE
Fetching mosaic from /mosaics/72C0212E67A08BCE
{
  "version": 1,
  "id": "72C0212E67A08BCE",
  "supply": "8323465708553682",
  "startHeight": "1",
  "ownerAddress": "9889432DE263BB8FE88444A4DA28D3609BD8BB8FAE18AE95",
  "revision": 1,
  "flags": 2,
  "divisibility": 6,
  "duration": "0"
}
Hashed value: DABD58337F1BA84742EE4F0EBDC17BC9C40E07AC3A7C7D5646DD558514162EA8
Encoded key: 3A4C540D7E6C7DF3E924E9F1B7D468FF771301AF00B0476E2B7E511379BB559E
Current height: 3220296
Fetching block from /blocks/3220296
State hash: BE6796E2F0E57A9F56910F7AC6D50BB730698184AAA29CED9167CFBD3F3C6A78
Fetching tree path from /mosaics/72C0212E67A08BCE/merkle
Tree path: 5 nodes
  [0] branch  links: [0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F]  -> follow 3
  [1] branch  links: [0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F]  -> follow A
  [2] branch  links: [0,1,3,4,5,6,7,8,9,A,B,C,D,E,F]  -> follow 4
  [3] branch  links: [4,5,7,C,E]  -> follow C
  [4] leaf  path: 540D7E6C7DF3E924E9F1B7D468FF771301AF00B0476E2B7E511379BB559E  value: DABD58337F1BA84742EE4F0EBDC17BC9C40E07AC3A7C7D5646DD558514162EA8
Mosaic 72C0212E67A08BCE state verified at height 3220296

出力の主なポイント:

  • モザイク定義 (3-14行目): /mosaics/{mosaicId} GET によって返された完全なモザイク定義。証明のためにシリアライズされるすべてのフィールドを示しています。

  • ハッシュ化された値とエンコードされたキー (15-16行目): パトリシア木で値およびエンコードされたキーとして使用される SHA3-256 ハッシュ。

  • ステートハッシュ (19行目): すべてのチェーン状態を含むブロックヘッダーのハッシュ。 証明は、ツリーパスがこのハッシュまで遡ることを確認し、検証を特定のブロックに結び付けます。

  • ツリーパス (22-26行目): デシリアライズされたルートからリーフまでのパス。 各ブランチノードは、そのリンクとどのニブルがたどられたか( -> follow )を示しています。 すべてのブランチノードはエンコードされたキーの1つのニブル(16進数)を消費し、リーフはどのブランチでも消費されなかった残りのニブルを保存します。 ニブル 3A4C をリーフのパス 540D7E...559E と連結すると、16行目の完全なエンコードされたキーが再構成されます。 リーフの値は、15行目のハッシュ化された値と一致します。 このツリー構造の視覚的な表現については、テキストブックの ステートハッシュ の図を参照してください。

  • 証明結果 (27行目): 証明が成功し、ノードが提供するモザイク定義が高さ 3220296 でチェーンに保存されているものと同一であることが確認されました。

結論⚓︎

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

ステップ 関連ドキュメント
モザイク定義の取得 /mosaics/{mosaicId} GET
キーと値のハッシュの計算 MosaicEntry
ブロックステートハッシュの取得 /chain/info GET/blocks/{height} GET
ツリーパスの取得 /mosaics/{mosaicId}/merkle GETdeserializePatriciaTreeNodes
証明の検証

次のステップ⚓︎

同じ手法は、モザイクだけでなく、ステートハッシュ内の任意の サブキャッシュ にも適用されます。

各サブキャッシュには、バイナリレイアウト(例: AccountStateRootNamespaceHistoryMetadataEntry )を定義する独自の catbuffer スキーマ と、ツリーパスを取得するための対応する /merkle エンドポイントがあります。

特定のトランザクションがブロックの一部であることを検証するには、代わりに ブロックへのトランザクション包含の証明 チュートリアルを参照してください。