{"id":"a246299d3743ae43c83cf37332d1a55d9a44dfbc54c1feff546a2d70f1364948","pubkey":"7ed7d5c3abf06fa1c00f71f879856769f46ac92354c129b3ed5562506927e200","created_at":1777762665,"kind":30817,"tags":[["d","event-storage-specification"],["title","Event Storage Specification"],["k","5"],["client","nostrhub.io"]],"content":"# Nostr Event Storage Specification\n\n## 1. Common Storage Rules\n\nThese rules apply to both client and relay implementations.\n\n### Rule 1.1: Event Deduplication\n\nEvents with identical `id` fields are considered duplicates. Store only one copy.\n\n```\non_event_received(event):\n  if exists(event.id):\n    return DUPLICATE\n  else:\n    store(event)\n    return STORED\n```\n\n**Example:**\n\n```\nFirst:  id=\"4376c65d...\"  → STORED\nSecond: id=\"4376c65d...\"  → DUPLICATE (rejected)\n```\n\n### Rule 1.2: Replaceable Event Semantics\n\nFor kinds 0, 3, and 10000-19999, keep only the newest event per (kind, pubkey) pair.\n\n```\nis_replaceable(kind):\n  return kind in [0, 3] or (10000 <= kind < 20000)\n\non_replaceable_event(event):\n  key = (event.kind, event.pubkey)\n  existing = find_by_key(key)\n  \n  if existing and existing.created_at >= event.created_at:\n    return REPLACED  // incoming is older\n  \n  if existing:\n    delete(existing)\n  \n  store(event)\n  return STORED\n```\n\n**Example:**\n\n```\nStore: kind=0, pubkey=\"abc123\", created_at=1000\nStore: kind=0, pubkey=\"abc123\", created_at=1500\nResult: Only second event remains (timestamp 1500)\n```\n\n### Rule 1.3: Addressable Event Semantics\n\nFor kinds 30000-39999, keep only the newest event per (kind, pubkey, d-tag) tuple.\n\n```\nis_addressable(kind):\n  return 30000 <= kind < 40000\n\nextract_d_tag(event):\n  for tag in event.tags:\n    if tag[0] == \"d\":\n      return tag[1]\n  return \"\"\n\non_addressable_event(event):\n  d_value = extract_d_tag(event)\n  key = (event.kind, event.pubkey, d_value)\n  existing = find_by_key(key)\n  \n  if existing and existing.created_at >= event.created_at:\n    return REPLACED\n  \n  if existing:\n    delete(existing)\n  \n  store(event)\n  return STORED\n```\n\n**Example:**\n\n```json\n{\n  \"kind\": 30023,\n  \"pubkey\": \"abc123...\",\n  \"tags\": [[\"d\", \"article-1\"]],\n  \"created_at\": 1000\n}\n```\n\nAddress: `(30023, \"abc123...\", \"article-1\")`\n\n### Rule 1.4: Ephemeral Event Handling\n\nEvents with kinds 20000-29999 must not be stored.\n\n```\nis_ephemeral(kind):\n  return 20000 <= kind < 30000\n\non_event_received(event):\n  if is_ephemeral(event.kind):\n    return EPHEMERAL  // forward only, never store\n```\n\n**Example:**\n\n```\nkind=25000 → Never store, only forward to subscribers\nkind=15000 → Store normally\n```\n\n### Rule 1.5: Deletion Enforcement\n\nKind 5 events delete previously stored events by the same author.\n\n```\non_deletion_event(event):\n  if event.kind != 5:\n    return\n  \n  // Extract event IDs from 'e' tags\n  deleted_ids = []\n  for tag in event.tags:\n    if tag[0] == \"e\" and len(tag) >= 2:\n      deleted_ids.append(tag[1])\n  \n  // Delete matching events\n  for id in deleted_ids:\n    existing = find_by_id(id)\n    if existing and existing.pubkey == event.pubkey:\n      delete(existing)\n      mark_deleted(id, event.pubkey)\n```\n\n**Example deletion event:**\n\n```json\n{\n  \"kind\": 5,\n  \"pubkey\": \"abc123...\",\n  \"tags\": [\n    [\"e\", \"4376c65d...\"],\n    [\"e\", \"5c83da77...\"]\n  ]\n}\n```\n\nDeletes events `4376c65d...` and `5c83da77...` if authored by `abc123...`\n\n### Rule 1.6: Deletion by Address\n\nKind 5 events can delete addressable events using 'a' tags.\n\n```\non_deletion_event(event):\n  // Extract addresses from 'a' tags\n  deleted_addresses = []\n  for tag in event.tags:\n    if tag[0] == \"a\" and len(tag) >= 2:\n      deleted_addresses.append(tag[1])\n  \n  for address in deleted_addresses:\n    (kind, pubkey, d_value) = parse_address(address)\n    if pubkey == event.pubkey:\n      existing = find_by_address(kind, pubkey, d_value)\n      if existing and existing.created_at < event.created_at:\n        delete(existing)\n        mark_deleted(address, event.pubkey, event.created_at)\n```\n\n**Example:**\n\n```json\n{\n  \"kind\": 5,\n  \"tags\": [\n    [\"a\", \"30023:abc123...:article-1\"]\n  ]\n}\n```\n\n### Rule 1.7: Prevent Re-insertion After Deletion\n\nOnce an event is deleted, block future attempts to store it.\n\n```\non_event_received(event):\n  if is_deleted(event.id, event.pubkey):\n    return BLOCKED\n  \n  if is_addressable(event.kind):\n    address = make_address(event.kind, event.pubkey, extract_d_tag(event))\n    deletion_timestamp = get_deletion_timestamp(address, event.pubkey)\n    if deletion_timestamp and event.created_at < deletion_timestamp:\n      return BLOCKED\n```\n\n### Rule 1.8: Filter Matching Logic\n\nEvents match a filter if they satisfy all specified conditions.\n\n```\nmatches_filter(event, filter):\n  // ids: prefix match\n  if filter.ids:\n    if not any(event.id.startswith(prefix) for prefix in filter.ids):\n      return false\n  \n  // authors: prefix match\n  if filter.authors:\n    if not any(event.pubkey.startswith(prefix) for prefix in filter.authors):\n      return false\n  \n  // kinds: exact match\n  if filter.kinds:\n    if event.kind not in filter.kinds:\n      return false\n  \n  // since/until: timestamp range\n  if filter.since and event.created_at < filter.since:\n    return false\n  if filter.until and event.created_at > filter.until:\n    return false\n  \n  // tag filters: must have matching tag\n  for (tag_name, values) in filter.tag_filters:\n    found = false\n    for tag in event.tags:\n      if tag[0] == tag_name and tag[1] in values:\n        found = true\n        break\n    if not found:\n      return false\n  \n  return true\n```\n\n**Example filter:**\n\n```json\n{\n  \"kinds\": [1],\n  \"authors\": [\"abc123\"],\n  \"#e\": [\"4376c65d...\"]\n}\n```\n\nMatches: kind=1, pubkey starts with \"abc123\", has tag [\"e\", \"4376c65d...\"]\n\n### Rule 1.9: Multi-Filter OR Logic\n\nMultiple filters in a query combine with OR logic.\n\n```\nmatches_any_filter(event, filters):\n  for filter in filters:\n    if matches_filter(event, filter):\n      return true\n  return false\n```\n\n**Example:**\n\n```json\n[\n  {\"kinds\": [1]},\n  {\"kinds\": [6], \"authors\": [\"abc123\"]}\n]\n```\n\nMatches: All kind 1 events OR (kind 6 events from \"abc123\")\n\n### Rule 1.10: Result Ordering\n\nReturn events in descending timestamp order, using ID as tiebreaker.\n\n```\nsort_events(events):\n  return sorted(events, \n    key=lambda e: (-e.created_at, e.id))\n```\n\n**Example:**\n\n```\nEvent A: created_at=1500, id=\"aaa...\"\nEvent B: created_at=1500, id=\"bbb...\"\nEvent C: created_at=1200, id=\"ccc...\"\n\nOrder: [A, B, C]\n```\n\n### Rule 1.11: Limit Application\n\nWhen a filter specifies `limit`, return at most that many results.\n\n```\napply_limit(events, filter):\n  if filter.limit:\n    return events[:filter.limit]\n  return events\n```\n\n### Rule 1.12: Tag Index Extraction\n\nIndex single-letter tags for efficient querying.\n\n```\nextract_indexable_tags(event):\n  indexed = []\n  for tag in event.tags:\n    if len(tag) >= 2 and len(tag[0]) == 1:\n      indexed.append((tag[0], tag[1]))\n  return indexed\n```\n\n**Example:**\n\n```json\n\"tags\": [\n  [\"e\", \"4376c65d...\", \"wss://relay.com\"],\n  [\"p\", \"abc123...\"],\n  [\"expiration\", \"1673433737\"]\n]\n```\n\nIndexed: `[(\"e\", \"4376c65d...\"), (\"p\", \"abc123...\")]`\n\n---\n\n## 2. Client Storage Rules\n\nThese rules apply only to client implementations.\n\n### Rule 2.1: Memory-Bounded Storage\n\nClients must limit memory consumption through eviction.\n\n```\nmax_events = 10000  // configurable\n\non_event_stored():\n  if count_events() > max_events:\n    evict_oldest_unclaimed()\n```\n\n### Rule 2.2: Claiming System\n\nTrack which subscriptions reference each event to prevent premature eviction.\n\n```\nclaims = Map<event_id, Set<subscription_id>>\n\nclaim_event(event_id, subscription_id):\n  claims[event_id].add(subscription_id)\n\nrelease_claim(event_id, subscription_id):\n  claims[event_id].remove(subscription_id)\n\nis_claimed(event_id):\n  return claims[event_id] is not empty\n\nevict_oldest_unclaimed():\n  for event in lru_order():\n    if not is_claimed(event.id):\n      delete(event)\n      return\n```\n\n### Rule 2.3: Subscription Deduplication\n\nIdentical subscriptions share the same underlying query.\n\n```\nactive_subscriptions = Map<filter_hash, Observable>\n\nsubscribe(filters):\n  hash = hash_filters(filters)\n  \n  if active_subscriptions.contains(hash):\n    return active_subscriptions[hash]\n  \n  observable = create_query_observable(filters)\n  active_subscriptions[hash] = observable\n  return observable\n```\n\n### Rule 2.4: Reactive Updates\n\nWhen new events arrive, notify all matching subscriptions immediately.\n\n```\non_event_stored(event):\n  for (subscription_id, filters) in active_subscriptions:\n    if matches_any_filter(event, filters):\n      emit_to_subscription(subscription_id, event)\n```\n\n### Rule 2.5: Optional Validation\n\nClients may skip signature verification for events from trusted sources.\n\n```\non_event_received(event, source):\n  if is_trusted_source(source):\n    store(event)  // skip validation\n  else:\n    if validate_signature(event):\n      store(event)\n    else:\n      reject(event)\n```\n\n### Rule 2.6: Loader Integration\n\nWhen queried events are missing, invoke loaders to fetch from network.\n\n```\nget_event(event_id):\n  event = find_by_id(event_id)\n  if event:\n    return event\n  \n  if event_loader:\n    fetched = event_loader(event_id)\n    if fetched:\n      store(fetched)\n      return fetched\n  \n  return null\n```\n\n### Rule 2.7: Metadata Decoration\n\nClients may annotate events with runtime metadata without persisting it.\n\n```\n// Store metadata in separate map, not in event object\nmetadata = WeakMap<Event, Metadata>\n\nset_metadata(event, key, value):\n  if not metadata.has(event):\n    metadata.set(event, {})\n  metadata.get(event)[key] = value\n\n// Example metadata: relay hints, cache flags\n```\n\n---\n\n## 3. Relay Storage Rules\n\nThese rules apply only to relay implementations.\n\n### Rule 3.1: Full Validation Pipeline\n\nRelays must validate every event before storage.\n\n```\non_event_received(event):\n  // Step 1: Structure validation\n  if not validate_structure(event):\n    return [\"OK\", event.id, false, \"invalid: malformed structure\"]\n  \n  // Step 2: ID validation\n  computed_id = compute_event_id(event)\n  if computed_id != event.id:\n    return [\"OK\", event.id, false, \"invalid: incorrect id\"]\n  \n  // Step 3: Signature validation\n  if not verify_signature(event):\n    return [\"OK\", event.id, false, \"invalid: signature verification failed\"]\n  \n  // Step 4: Store\n  result = store(event)\n  return [\"OK\", event.id, true, \"\"]\n```\n\n### Rule 3.2: Durable Storage\n\nAll stored events must survive process restart.\n\n```\n// Use persistent storage backend\n// - Relational: SQLite, PostgreSQL, MySQL\n// - Key-Value: LMDB, Badger\n// - Ensure write-ahead logging or equivalent durability\n```\n\n### Rule 3.3: EOSE Semantics\n\nSend EOSE after delivering all stored events matching a subscription.\n\n```\non_subscription(subscription_id, filters):\n  stored_events = query_stored(filters)\n  for event in stored_events:\n    send([\"EVENT\", subscription_id, event])\n  \n  send([\"EOSE\", subscription_id])\n  \n  // Continue sending new matching events\n```\n\n**Example:**\n\n```\nClient: [\"REQ\", \"sub1\", {\"kinds\": [1], \"limit\": 5}]\nRelay:  [\"EVENT\", \"sub1\", {...}]  // stored event 1\nRelay:  [\"EVENT\", \"sub1\", {...}]  // stored event 2\nRelay:  [\"EOSE\", \"sub1\"]\nRelay:  [\"EVENT\", \"sub1\", {...}]  // new real-time event\n```\n\n### Rule 3.4: Concurrent Client Support\n\nHandle multiple simultaneous connections without data corruption.\n\n```\n// Use appropriate concurrency primitives\n// - Relational: Database transactions (SERIALIZABLE isolation)\n// - Key-Value: Explicit mutexes or lock-free data structures\n// - Read operations should not block writes\n```\n\n### Rule 3.5: Per-Filter Limit Enforcement\n\nWhen multiple filters have limits, apply each limit independently before combining.\n\n```\nquery_multi_filter(filters):\n  results = Set()\n  for filter in filters:\n    batch = query_single_filter(filter)\n    if filter.limit:\n      batch = batch[:filter.limit]\n    results.union(batch)\n  return sort_events(results)\n```\n\n**Example:**\n\n```json\n[\n  {\"kinds\": [1], \"limit\": 10},\n  {\"kinds\": [6], \"limit\": 5}\n]\n```\n\nReturns: Up to 10 kind-1 events + up to 5 kind-6 events\n\n### Rule 3.6: Write Confirmation\n\nSend OK message after each EVENT command.\n\n```\non_event_command([\"EVENT\", event]):\n  result = process_event(event)\n  \n  if result == STORED:\n    send([\"OK\", event.id, true, \"\"])\n  elif result == DUPLICATE:\n    send([\"OK\", event.id, true, \"duplicate: already stored\"])\n  elif result == BLOCKED:\n    send([\"OK\", event.id, false, \"blocked: event deleted\"])\n  elif result == INVALID:\n    send([\"OK\", event.id, false, \"invalid: \" + reason])\n```\n\n### Rule 3.7: Subscription Cleanup\n\nSupport CLOSE command to end subscriptions.\n\n```\non_close_command([\"CLOSE\", subscription_id]):\n  remove_subscription(subscription_id)\n  // Optionally send confirmation\n  send([\"CLOSED\", subscription_id, \"subscription ended\"])\n```\n\n---\n\n## 4. Optional Features\n\n### Optional Rule 4.1: Expiration Support (NIP-40)\n\nStore and honor expiration timestamps.\n\n```\nextract_expiration(event):\n  for tag in event.tags:\n    if tag[0] == \"expiration\" and len(tag) >= 2:\n      return parse_int(tag[1])\n  return null\n\non_event_received(event):\n  expiration = extract_expiration(event)\n  if expiration and current_timestamp() > expiration:\n    return REJECTED  // already expired\n  \n  store(event)\n  \n  if expiration:\n    schedule_deletion(event.id, expiration)\n\nschedule_deletion(event_id, timestamp):\n  at_time(timestamp):\n    delete(event_id)\n```\n\n**Example:**\n\n```json\n{\n  \"tags\": [[\"expiration\", \"1673433737\"]],\n  \"created_at\": 1673347337\n}\n```\n\nEvent expires 24 hours after creation.\n\n### Optional Rule 4.2: Full-Text Search\n\nIndex content field for text queries.\n\n```\non_event_stored(event):\n  if is_searchable(event):\n    add_to_search_index(event.id, event.content)\n\nquery_with_search(filter):\n  if filter.search:\n    matching_ids = search_index.query(filter.search)\n    events = [find_by_id(id) for id in matching_ids]\n    events = [e for e in events if matches_filter(e, filter)]\n    return events\n  else:\n    return normal_query(filter)\n```\n\n**Example filter:**\n\n```json\n{\n  \"kinds\": [1],\n  \"search\": \"bitcoin protocol\"\n}\n```\n\n### Optional Rule 4.3: Event Counting (NIP-45)\n\nSupport COUNT command without returning full events.\n\n```\non_count_command([\"COUNT\", subscription_id, ...filters]):\n  count = 0\n  for filter in filters:\n    count += count_matching(filter)\n  \n  send([\"COUNT\", subscription_id, {\"count\": count}])\n```\n\n### Optional Rule 4.4: Proof of Work Validation (NIP-13)\n\nVerify proof-of-work difficulty claims.\n\n```\nvalidate_pow(event):\n  for tag in event.tags:\n    if tag[0] == \"nonce\" and len(tag) >= 3:\n      target_difficulty = parse_int(tag[2])\n      actual_difficulty = count_leading_zero_bits(event.id)\n      return actual_difficulty >= target_difficulty\n  return true  // no PoW requirement\n```\n\n### Optional Rule 4.5: Compression (Relay Only)\n\nCompress stored event data to reduce disk usage.\n\n```\nstore_event(event):\n  json = serialize(event)\n  compressed = compress(json, algorithm=zstd)\n  \n  write_to_storage(event.id, compressed)\n\nretrieve_event(event_id):\n  compressed = read_from_storage(event_id)\n  json = decompress(compressed)\n  return deserialize(json)\n```\n\n### Optional Rule 4.6: Read Replicas (Relay Only)\n\nDistribute read load across multiple database instances.\n\n```\n// Write to master\nstore_event(event):\n  master_db.insert(event)\n\n// Read from replica (round-robin or random)\nquery_events(filter):\n  replica = select_replica()\n  return replica.query(filter)\n```\n\n### Optional Rule 4.7: Negentropy Set Reconciliation (Relay Only)\n\nSupport efficient synchronization protocol.\n\n```\n// Maintain pre-computed BTree fingerprints\non_event_stored(event):\n  for cached_filter in negentropy_cache:\n    if matches_filter(event, cached_filter):\n      cached_filter.btree.insert(event.id, event.created_at)\n\non_negentropy_request(filter, client_btree):\n  server_btree = get_or_build_btree(filter)\n  differences = compute_differences(client_btree, server_btree)\n  send_differences(differences)\n```\n\n---\n\n## 5. Special Cases\n\n### Special Case 5.1: Timestamp Ties\n\nWhen sorting events with identical timestamps, use event ID as tiebreaker.\n\n```\n// Required for deterministic ordering\nsort_key(event):\n  return (-event.created_at, event.id)  // descending time, ascending ID\n```\n\n### Special Case 5.2: Empty Filters\n\nA filter with no fields matches all events.\n\n```\nfilter = {}  // matches everything\n```\n\nClient may apply default limit to prevent overwhelming results.\n\n### Special Case 5.3: Zero-Length d-tag\n\nAddressable events without a d-tag use empty string as identifier.\n\n```\nextract_d_tag(event):\n  for tag in event.tags:\n    if tag[0] == \"d\":\n      return tag[1] if len(tag) >= 2 else \"\"\n  return \"\"  // no d-tag found\n```\n\n**Example:**\n\n```json\n{\"kind\": 30023, \"tags\": [[\"d\", \"\"]]}\n{\"kind\": 30023, \"tags\": []}\n```\n\nBoth have address: `30023:<pubkey>:`\n\n### Special Case 5.4: Kind 5 Self-Deletion\n\nA deletion event can reference its own ID in e-tags.\n\n```\non_deletion_event(event):\n  // Process deletions normally\n  process_e_tags(event)\n  \n  // Then store the deletion event itself\n  // (it may delete itself, which is valid)\n  store(event)\n```\n\n### Special Case 5.5: Replacement Timestamp Ties\n\nWhen replaceable events have identical timestamps, keep lexicographically lower ID.\n\n```\non_replaceable_event(event):\n  existing = find_replaceable(event.kind, event.pubkey)\n  \n  if existing:\n    if existing.created_at > event.created_at:\n      return REPLACED  // existing is newer\n    elif existing.created_at == event.created_at:\n      if existing.id < event.id:\n        return REPLACED  // existing ID wins tie\n  \n  delete(existing)\n  store(event)\n```\n\n### Special Case 5.6: Tag Value Limits\n\nImplementations should handle large tag values gracefully.\n\n```\n// Truncate or reject events with excessively large tags\nmax_tag_value_length = 1024  // configurable\n\nvalidate_tags(event):\n  for tag in event.tags:\n    for value in tag:\n      if len(value) > max_tag_value_length:\n        return false  // or truncate\n  return true\n```\n\n### Special Case 5.7: Multiple d-tags\n\nIf an event has multiple d-tags, use the first one.\n\n```\nextract_d_tag(event):\n  for tag in event.tags:\n    if tag[0] == \"d\" and len(tag) >= 2:\n      return tag[1]  // return first match\n  return \"\"\n```\n\n---\n\n## 6. Implementation Considerations\n\n### Consideration 6.1: Index Selection\n\nChoose appropriate indexes based on query patterns.\n\n**Essential indexes:**\n\n- Primary: `id` (unique)\n- Kind: `(kind, created_at DESC)`\n- Author: `(pubkey, created_at DESC)`\n- Time: `(created_at DESC)`\n- Tags: `(tag_name, tag_value, created_at DESC)`\n\n**Compound indexes for common patterns:**\n\n- Author + Kind: `(pubkey, kind, created_at DESC)`\n- Replaceable: `(kind, pubkey)` where kind is replaceable\n- Addressable: `(kind, pubkey, d_tag)` where kind is addressable\n\n### Consideration 6.2: Batch Processing\n\nGroup writes into transactions to reduce overhead.\n\n```\nbatch_size = 100\npending_events = []\n\non_event_received(event):\n  pending_events.append(event)\n  \n  if len(pending_events) >= batch_size:\n    transaction:\n      for e in pending_events:\n        store(e)\n    pending_events.clear()\n```\n\n### Consideration 6.3: Lazy Tag Indexing\n\nFor memory-constrained clients, build tag indexes on-demand.\n\n```\ntag_indexes = LRU_Cache<(tag_name, tag_value), Set<event_id>>\n\nquery_tag(tag_name, tag_value):\n  key = (tag_name, tag_value)\n  \n  if not tag_indexes.contains(key):\n    // Build index on first access\n    matching = []\n    for event in all_events():\n      for tag in event.tags:\n        if tag[0] == tag_name and tag[1] == tag_value:\n          matching.append(event.id)\n    tag_indexes[key] = matching\n  \n  return tag_indexes[key]\n```\n\n### Consideration 6.4: Binary Storage\n\nFor relay implementations, consider binary encoding to reduce storage size.\n\n```\n// Pack event into binary format\npacked_event = pack(\n  id_bytes,      // 32 bytes\n  pubkey_bytes,  // 32 bytes\n  created_at,    // 8 bytes (uint64)\n  kind,          // 4 bytes (uint32)\n  tags_encoded,  // variable\n  content_length,// 4 bytes (uint32)\n  // content and sig stored separately\n)\n```\n\n### Consideration 6.5: Connection Pooling\n\nRelays should manage database connections efficiently.\n\n```\npool_config:\n  min_connections: 5\n  max_connections: 50\n  idle_timeout: 60s\n  connection_lifetime: 3600s\n\nquery(sql):\n  conn = pool.acquire()\n  try:\n    result = conn.execute(sql)\n    return result\n  finally:\n    pool.release(conn)\n```\n\n### Consideration 6.6: Rate Limiting\n\nProtect relay resources from abuse.\n\n```\nlimits = Map<client_ip, TokenBucket>\n\non_client_request(client_ip, request):\n  bucket = limits[client_ip]\n  \n  if not bucket.consume(1):\n    send([\"NOTICE\", \"rate limit exceeded\"])\n    disconnect(client_ip)\n    return\n  \n  process_request(request)\n```\n\n### Consideration 6.7: Storage Migration\n\nPlan for schema changes and data migration.\n\n```\n// Version stored schema\nschema_version = 3\n\non_startup():\n  stored_version = read_schema_version()\n  \n  if stored_version < schema_version:\n    migrate(from=stored_version, to=schema_version)\n    update_schema_version(schema_version)\n```","sig":"a6cd6296573e500799a1bf85e4d717714a4675285a8b719051e91ff6cda24dc45221ce45acaf765ea420732be5263e9ffc06e4d20b8ae8e47d679cedcd4c7ac0"}