Get through attestation

This commit is contained in:
Scott Shawcroft 2024-09-25 16:39:07 -07:00
parent 391fc0df27
commit 672c8643ea
No known key found for this signature in database
6 changed files with 334 additions and 59 deletions

View file

@ -5,11 +5,11 @@ import enum
import hashlib
import pathlib
import json
import os
import struct
import time
import cryptography
import ecdsa
from typing import Optional
@ -18,6 +18,18 @@ from . import interaction_model
from . import session
from . import tlv
TEST_CERTS = pathlib.Path(
"/home/tannewt/repos/esp-matter/connectedhomeip/connectedhomeip/credentials/test/attestation/"
)
TEST_PAI_CERT_DER = TEST_CERTS / "Chip-Test-PAI-FFF1-8000-Cert.der"
TEST_PAI_CERT_PEM = TEST_CERTS / "Chip-Test-PAI-FFF1-8000-Cert.pem"
TEST_DAC_CERT_DER = TEST_CERTS / "Chip-Test-DAC-FFF1-8000-0000-Cert.der"
TEST_DAC_CERT_PEM = TEST_CERTS / "Chip-Test-DAC-FFF1-8000-0000-Cert.pem"
TEST_DAC_KEY_DER = TEST_CERTS / "Chip-Test-DAC-FFF1-8000-0000-Key.der"
TEST_DAC_KEY_PEM = TEST_CERTS / "Chip-Test-DAC-FFF1-8000-0000-Key.pem"
TEST_CD_CERT_DER = pathlib.Path("certification_declaration.der")
__version__ = "0.0.0"
# Section 4.11.2
@ -192,9 +204,9 @@ class MessageReceptionState:
class MessageCounter:
def __init__(self, starting_value=None):
def __init__(self, starting_value=None, random_source=None):
if starting_value is None:
starting_value = os.urandom(4)
starting_value = random_source.urandom(4)
starting_value = struct.unpack("<I", starting_value)[0]
starting_value >>= 4
starting_value += 1
@ -305,7 +317,7 @@ class UnsecuredSessionContext:
class SecureSessionContext:
def __init__(self, socket, local_session_id):
def __init__(self, random_source, socket, local_session_id):
self.session_type = None
"""Records whether the session was established using CASE or PASE."""
self.session_role_initiator = False
@ -320,7 +332,7 @@ class SecureSessionContext:
"""Encrypts data in messages sent from the session establishment responder to the initiator."""
self.shared_secret = None
"""Computed during the CASE protocol execution and re-used when CASE session resumption is implemented."""
self.local_message_counter = MessageCounter()
self.local_message_counter = MessageCounter(random_source=random_source)
"""Secure Session Message Counter for outbound messages."""
self.message_reception_state = None
"""Provides tracking for the Secure Session Message Counter of the remote"""
@ -736,7 +748,7 @@ class StatusReport:
class SessionManager:
def __init__(self, socket):
def __init__(self, random_source, socket):
persist_path = pathlib.Path("counters.json")
if persist_path.exists():
self.nonvolatile = json.loads(persist_path.read_text())
@ -745,17 +757,22 @@ class SessionManager:
self.nonvolatile["check_in_counter"] = None
self.nonvolatile["group_encrypted_data_message_counter"] = None
self.nonvolatile["group_encrypted_control_message_counter"] = None
self.unencrypted_message_counter = MessageCounter()
self.unencrypted_message_counter = MessageCounter(random_source=random_source)
self.group_encrypted_data_message_counter = MessageCounter(
self.nonvolatile["group_encrypted_data_message_counter"]
self.nonvolatile["group_encrypted_data_message_counter"],
random_source=random_source,
)
self.group_encrypted_control_message_counter = MessageCounter(
self.nonvolatile["group_encrypted_control_message_counter"]
self.nonvolatile["group_encrypted_control_message_counter"],
random_source=random_source,
)
self.check_in_counter = MessageCounter(
self.nonvolatile["check_in_counter"], random_source=random_source
)
self.check_in_counter = MessageCounter(self.nonvolatile["check_in_counter"])
self.unsecured_session_context = {}
self.secure_session_contexts = ["reserved"]
self.socket = socket
self.random = random_source
def _increment(self, value):
return (value + 1) % 0xFFFFFFFF
@ -836,7 +853,7 @@ class SessionManager:
session_id = self.secure_session_contexts.index(None)
self.secure_session_contexts[session_id] = SecureSessionContext(
self.socket, session_id
self.random, self.socket, session_id
)
return self.secure_session_contexts[session_id]
@ -887,41 +904,115 @@ class GeneralCommissioningCluster(data_model.GeneralCommissioningCluster):
self.basic_commissioning_info = basic_commissioning_info
def arm_fail_safe(
self, args: data_model.GeneralCommissioningCluster.ArmFailSafe
self, session, args: data_model.GeneralCommissioningCluster.ArmFailSafe
) -> data_model.GeneralCommissioningCluster.ArmFailSafeResponse:
response = data_model.GeneralCommissioningCluster.ArmFailSafeResponse()
response.ErrorCode = data_model.CommissioningErrorEnum.OK
return response
def set_regulatory_config(
self, args: data_model.GeneralCommissioningCluster.SetRegulatoryConfig
self, session, args: data_model.GeneralCommissioningCluster.SetRegulatoryConfig
) -> data_model.GeneralCommissioningCluster.SetRegulatoryConfigResponse:
response = data_model.GeneralCommissioningCluster.SetRegulatoryConfigResponse()
response.ErrorCode = data_model.CommissioningErrorEnum.OK
return response
class AttestationElements(tlv.Structure):
certification_declaration = tlv.OctetStringMember(0x01, max_length=400)
attestation_nonce = tlv.OctetStringMember(0x02, max_length=32)
timestamp = tlv.IntMember(0x03, signed=False, octets=4)
firmware_information = tlv.OctetStringMember(0x04, max_length=16, optional=True)
"""Used for secure boot. We don't support it."""
class NOCSRElements(tlv.Structure):
csr = tlv.OctetStringMember(0x01, max_length=1024)
CSRNonce = tlv.OctetStringMember(0x02, max_length=32)
# Skip vendor reserved
class NodeOperationalCredentialsCluster(data_model.NodeOperationalCredentialsCluster):
def __init__(self):
self.dac_key = ecdsa.keys.SigningKey.from_der(
TEST_DAC_KEY_DER.read_bytes(), hashfunc=hashlib.sha256
)
self.new_key_for_update = False
def certificate_chain_request(
self, args: data_model.NodeOperationalCredentialsCluster.CertificateChainRequest
self,
session,
args: data_model.NodeOperationalCredentialsCluster.CertificateChainRequest,
) -> data_model.NodeOperationalCredentialsCluster.CertificateChainResponse:
response = (
data_model.NodeOperationalCredentialsCluster.CertificateChainResponse()
)
if args.CertificateType == data_model.CertificateChainTypeEnum.PAI:
print("PAI")
response.Certificate = TEST_PAI_CERT_DER.read_bytes()
elif args.CertificateType == data_model.CertificateChainTypeEnum.DAC:
print("DAC")
response.Certificate = b""
response.Certificate = TEST_DAC_CERT_DER.read_bytes()
return response
def attestation_request(
self, args: data_model.NodeOperationalCredentialsCluster.AttestationRequest
self,
session,
args: data_model.NodeOperationalCredentialsCluster.AttestationRequest,
) -> data_model.NodeOperationalCredentialsCluster.AttestationResponse:
print("attestation")
elements = AttestationElements()
elements.certification_declaration = TEST_CD_CERT_DER.read_bytes()
elements.attestation_nonce = args.AttestationNonce
elements.timestamp = int(time.time())
elements = elements.encode()
print("elements", len(elements), elements[:3].hex(" "))
print(
"challeng",
len(session.attestation_challenge),
session.attestation_challenge[:3].hex(" "),
)
attestation_tbs = elements.tobytes() + session.attestation_challenge
response = data_model.NodeOperationalCredentialsCluster.AttestationResponse()
response.AttestationElements = b""
response.AttestationSignature = b""
response.AttestationElements = elements
response.AttestationSignature = self.dac_key.sign_deterministic(
attestation_tbs,
hashfunc=hashlib.sha256,
sigencode=ecdsa.util.sigencode_string,
)
return response
def csr_request(
self, session, args: data_model.NodeOperationalCredentialsCluster.CsrRequest
) -> data_model.NodeOperationalCredentialsCluster.CsrResponse:
# Section 6.4.6.1
# CSR stands for Certificate Signing Request. A NOCSR is a Node Operational Certificate Signing Request
self.new_key_for_update = args.IsForUpdateNOC
# class CSRRequest(tlv.Structure):
# CSRNonce = tlv.OctetStringMember(0, 32)
# IsForUpdateNOC = tlv.BoolMember(1, optional=True, default=False)
# Generate a new key pair.
new_key_csr = b"TODO"
# Create a CSR to reply back with. Sign it with the new private key.
elements = NOCSRElements()
elements.csr = new_key_csr
elements.CSRNonce = args.CSRNonce
elements = elements.encode()
nocsr_tbs = elements.tobytes() + session.attestation_challenge
# class CSRResponse(tlv.Structure):
# NOCSRElements = tlv.OctetStringMember(0, RESP_MAX)
# AttestationSignature = tlv.OctetStringMember(1, 64)
response = data_model.NodeOperationalCredentialsCluster.CsrResponse()
response.NOCSRElements = elements
response.AttestationSignature = self.dac_key.sign_deterministic(
nocsr_tbs, hashfunc=hashlib.sha256, sigencode=ecdsa.util.sigencode_string
)
return response
@ -933,7 +1024,7 @@ class CircuitMatter:
random_source,
state_filename,
vendor_id=0xFFF1,
product_id=0,
product_id=0x8000,
):
self.socketpool = socketpool
self.mdns_server = mdns_server
@ -963,7 +1054,7 @@ class CircuitMatter:
self.socket.bind((UDP_IP, self.UDP_PORT))
self.socket.setblocking(False)
self.manager = SessionManager(self.socket)
self.manager = SessionManager(self.random, self.socket)
print(f"Listening on UDP port {self.UDP_PORT}")
@ -1039,10 +1130,10 @@ class CircuitMatter:
# report.AttributeStatus = astatus
return report
def invoke(self, cluster, path, fields, command_ref):
def invoke(self, session, cluster, path, fields, command_ref):
print("invoke", path)
response = interaction_model.InvokeResponseIB()
cdata = cluster.invoke(path, fields)
cdata = cluster.invoke(session, path, fields)
if cdata is None:
cstatus = interaction_model.CommandStatusIB()
cstatus.CommandPath = path
@ -1216,6 +1307,9 @@ class CircuitMatter:
elif protocol_opcode == SecureProtocolOpcode.ICD_CHECK_IN:
print("Received ICD Check-in")
elif message.protocol_id == ProtocolId.INTERACTION_MODEL:
secure_session_context = self.manager.secure_session_contexts[
message.session_id
]
if protocol_opcode == InteractionModelOpcode.READ_REQUEST:
print("Received Read Request")
read_request, _ = interaction_model.ReadRequestMessage.decode(
@ -1272,7 +1366,12 @@ class CircuitMatter:
cluster = self._endpoints[endpoint][path.Cluster]
path.Endpoint = endpoint
invoke_responses.append(
self.invoke(cluster, path, invoke.CommandFields)
self.invoke(
secure_session_context,
cluster,
path,
invoke.CommandFields,
)
)
else:
print(f"Cluster 0x{path.Cluster:02x} not found")
@ -1281,6 +1380,7 @@ class CircuitMatter:
cluster = self._endpoints[path.Endpoint][path.Cluster]
invoke_responses.append(
self.invoke(
secure_session_context,
cluster,
path,
invoke.CommandFields,

View file

@ -0,0 +1,128 @@
# This file should only be needed when generating certificates.
import hashlib
from . import tlv
from .data_model import Enum8
import ecdsa
from ecdsa import der
import pathlib
class CertificationType(Enum8):
DEVELOPMENT_AND_TEST = 0
PROVISIONAL = 1
OFFICIAL = 2
class CertificationDeclaration(tlv.Structure):
format_version = tlv.IntMember(0, signed=False, octets=1)
vendor_id = tlv.IntMember(1, signed=False, octets=2)
product_id_array = tlv.ArrayMember(
2, tlv.IntMember(0, signed=False, octets=2), max_length=100
)
device_type_id = tlv.IntMember(3, signed=False, octets=4)
certificate_id = tlv.UTF8StringMember(4, max_length=19)
security_level = tlv.IntMember(5, signed=False, octets=1)
security_information = tlv.IntMember(6, signed=False, octets=2)
version_number = tlv.IntMember(7, signed=False, octets=2)
certification_type = tlv.EnumMember(8, CertificationType)
dac_origin_vendor_id = tlv.IntMember(9, signed=False, octets=2, optional=True)
dac_origin_product_id = tlv.IntMember(10, signed=False, octets=2, optional=True)
authorized_paa_list = tlv.ArrayMember(
11, tlv.OctetStringMember(None, max_length=20), optional=True, max_length=10
)
def encode_set(*encoded_pieces):
total_len = sum([len(p) for p in encoded_pieces])
return b"\x31" + der.encode_length(total_len) + b"".join(encoded_pieces)
def generate_certificates(
vendor_id=0xFFF1, product_id=0x8000, device_type=22, prefix=None
):
declaration = CertificationDeclaration()
declaration.format_version = 1 # Always 1
declaration.vendor_id = vendor_id
declaration.product_id_array = [product_id]
declaration.device_type_id = 0x1234 # device_type
declaration.certificate_id = "ZIG20141ZB330001-24" # "CSA00000SWC00000-00"
declaration.security_level = 0 # Always 0
declaration.security_information = 0 # Always 0
declaration.version_number = 0x2694 # 1 # Always 1
declaration.certification_type = CertificationType.DEVELOPMENT_AND_TEST
declaration = declaration.encode()
for i in range(0, len(declaration), 16):
print(f"{i:08x}", declaration[i : i + 16].hex(" "))
# From: https://github.com/project-chip/matter.js/blob/main/packages/protocol/src/certificate/CertificationDeclarationManager.ts
# NIST256p is the same as secp256r1
private_key = ecdsa.keys.SigningKey.from_string(
b"\xae\xf3\x48\x41\x16\xe9\x48\x1e\xc5\x7b\xe0\x47\x2d\xf4\x1b\xf4\x99\x06\x4e\x50\x24\xad\x86\x9e\xca\x5e\x88\x98\x02\xd4\x80\x75",
curve=ecdsa.curves.NIST256p,
hashfunc=hashlib.sha256,
)
print(private_key.to_string().hex().upper())
subject_key_identifier = b"\x62\xfa\x82\x33\x59\xac\xfa\xa9\x96\x3e\x1c\xfa\x14\x0a\xdd\xf5\x04\xf3\x71\x60"
signature = private_key.sign_deterministic(
declaration,
hashfunc=hashlib.sha256,
sigencode=ecdsa.util.sigencode_der_canonize,
)
print("signature", signature.hex(" "))
certification_declaration = []
# version
certification_declaration.append(der.encode_integer(3))
# Digest algorithm
certification_declaration.append(
encode_set(der.encode_sequence(der.encode_oid(2, 16, 840, 1, 101, 3, 4, 2, 1)))
)
# encap content info
encap_content_info = []
# content type
encap_content_info.append(der.encode_oid(1, 2, 840, 113549, 1, 7, 1))
# content
encap_content_info.append(
der.encode_constructed(0, der.encode_octet_string(declaration))
)
certification_declaration.append(der.encode_sequence(*encap_content_info))
signer_info = []
# version
signer_info.append(der.encode_integer(3))
# subject key identifier
signer_info.append(
b"\x80"
+ der.encode_length(len(subject_key_identifier))
+ subject_key_identifier
)
# digest algorithm
signer_info.append(
der.encode_sequence(der.encode_oid(2, 16, 840, 1, 101, 3, 4, 2, 1))
)
# signature algorithm
signer_info.append(der.encode_sequence(der.encode_oid(1, 2, 840, 10045, 4, 3, 2)))
# signature
signer_info.append(der.encode_octet_string(signature))
certification_declaration.append(encode_set(der.encode_sequence(*signer_info)))
signed_data = []
signed_data.append(der.encode_oid(1, 2, 840, 113549, 1, 7, 2))
cd = der.encode_sequence(*certification_declaration)
signed_data.append(der.encode_constructed(0, cd))
cms_signed = der.encode_sequence(*signed_data)
return cms_signed
if __name__ == "__main__":
cd = generate_certificates()
pathlib.Path("certification_declaration.der").write_bytes(cd)
for i in range(0, len(cd), 16):
print(f"{i:08x}", cd[i : i + 16].hex(" "))
print(cd.hex(" "))

View file

@ -172,7 +172,9 @@ class Cluster:
if not field_name.startswith("_") and isinstance(descriptor, Command):
yield field_name, descriptor
def invoke(self, path, fields) -> Optional[interaction_model.CommandDataIB]:
def invoke(
self, session, path, fields
) -> Optional[interaction_model.CommandDataIB]:
found = False
for field_name, descriptor in self._commands():
if descriptor.command_id != path.Command:
@ -183,7 +185,7 @@ class Cluster:
print(arg)
command = getattr(self, field_name)
if callable(command):
result = command(arg)
result = command(session, arg)
else:
print(field_name, "not implemented")
return None
@ -458,7 +460,7 @@ class NodeOperationalCredentialsCluster(Cluster):
IsForUpdateNOC = tlv.BoolMember(1, optional=True, default=False)
class CSRResponse(tlv.Structure):
CSR = tlv.OctetStringMember(0, RESP_MAX)
NOCSRElements = tlv.OctetStringMember(0, RESP_MAX)
AttestationSignature = tlv.OctetStringMember(1, 64)
class AddNOC(tlv.Structure):

View file

@ -241,7 +241,8 @@ def compute_session_keys(Ke, secure_session_context):
tag_length=session.CRYPTO_AEAD_MIC_LENGTH_BYTES,
)
secure_session_context.attestation_challenge = keys[
2 * session.CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES :
2 * session.CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES : 3
* session.CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES
]

View file

@ -434,11 +434,43 @@ class NumberMember(Member[_NT, _OPT, _NULLABLE], Generic[_NT, _OPT, _NULLABLE]):
return f"{value}{unsigned}"
def encode_element_type(self, value):
# We don't adjust our encoding based on value size. We always use the bytes needed for the
# format.
if self.integer:
bit_length = value.bit_length()
if self.signed:
type = ElementType.SIGNED_INT
else:
type = ElementType.UNSIGNED_INT
length = 0 # in power of two
if bit_length <= 8:
length = 0
elif bit_length <= 16:
length = 1
elif bit_length <= 32:
length = 2
else:
length = 3
return type | length
return self._element_type
def encode_value_into(self, value, buffer, offset) -> int:
if self.integer:
bit_length = value.bit_length()
format_string = None
if bit_length <= 8:
format_string = "<b" if self.signed else "<B"
length = 1
elif bit_length <= 16:
format_string = "<h" if self.signed else "<H"
length = 2
elif bit_length <= 32:
format_string = "<i" if self.signed else "<I"
length = 4
else:
format_string = "<q" if self.signed else "<Q"
length = 8
struct.pack_into(format_string, buffer, offset, value)
return offset + length
# Float
struct.pack_into(self.format, buffer, offset, value)
return offset + self.max_value_length
@ -640,14 +672,16 @@ class ArrayMember(Member[_TLVStruct, _OPT, _NULLABLE]):
def __init__(
self,
tag,
substruct_class: Type[_TLVStruct],
substruct_class: Type[_TLVStruct, Member],
*,
max_length: Optional[int] = None,
optional: _OPT = False,
nullable: _NULLABLE = False,
**kwargs,
):
self.substruct_class = substruct_class
self.max_value_length = 1280
self.max_items = max_length
super().__init__(tag, optional=optional, nullable=nullable, **kwargs)
@staticmethod
@ -677,8 +711,11 @@ class ArrayMember(Member[_TLVStruct, _OPT, _NULLABLE]):
buffer[offset] = ElementType.STRUCTURE
elif isinstance(v, List):
buffer[offset] = ElementType.LIST
else:
raise NotImplementedError("Unknown type")
elif isinstance(self.substruct_class, Member):
buffer[offset] = self.substruct_class.encode_element_type(v)
print(offset, hex(buffer[offset]))
offset = self.substruct_class.encode_value_into(v, buffer, offset + 1)
continue
offset = v.encode_into(buffer, offset + 1)
buffer[offset] = ElementType.END_OF_CONTAINER
return offset + 1

View file

@ -1,27 +1,34 @@
["urandom", 683495367840819, 8, "TVCaLwZCw7U="]
["receive", 683498821765516, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "BAAAAF8dUQHg9sDgbF2ERwUgjxgAABUwASASKWbFjMO2TX2TttCy5g/kYva9Xkq3bUgRNk2ZJLC+MCUCVLskAwAoBDUFJQH0ASUCLAElA6APJAQRJAULJgYAAAMBJAcBGBg="]
["urandom", 683498851412134, 32, "MXZ+qUCRDQzGtLd/BdEFclIVyeARZatikL292J53wec="]
["send", 683498851505099, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AQAAAJ0RXQ7g9sDgbF2ERwIhjxgAAF8dUQEVMAEgEilmxYzDtk19k7bQsuYP5GL2vV5Kt21IETZNmSSwvjAwAiAxdn6pQJENDMa0t38F0QVyUhXJ4BFlq2KQvb3YnnfB5yUDAQA1BCYBECcAADACIObgj9CEx2MyPagRHuoX1OB32N8u1aKUpNKjb4b854YkGBg="]
["receive", 683498857474653, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "BAAAAGAdUQHg9sDgbF2ERwUijxgAABUwAUEE20SFW+IKRC2BOynyHg8nHPTv3+LVnM+u4ETTCvbrvnTgkJQNNj/qtVQNZWLUPqJlGYTqUqMqQtXrIkE5M4Ih6Bg="]
["randbelow", 683498857567458, 115792089210356248762697446949407573529996955224135760342422259061068512044369, 55163913805809046006520747928954323885679067894514874675221401915371371566658]
["send", 683498872841814, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AQAAAJ4RXQ7g9sDgbF2ERwIjjxgAAGAdUQEVMAFBBAbpQBc797XYmoP1v/VvwWAkH1y/0bDLthCBm0x0+NeykQt9xIvt80/Bs/ho3rZp+4j+S19V7CvxDJBpB47VKPowAiBwLzDgwDToYO6xBjEMzXZ7MVUWXPf90Xa9lWnufbGeaxg="]
["receive", 683498873254503, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "BAAAAGEdUQHg9sDgbF2ERwUkjxgAABUwASB8b9Xw7GZRd3GHmsZJ3Nv29JDKXZbinVKGP2m8Qt61Xxg="]
["send", 683498873329966, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AQAAAJ8RXQ7g9sDgbF2ERwJAjxgAAGEdUQEAAAAAAAAAAA=="]
["receive", 683498873509204, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADB0XwokT96ZD3xGZKllpiUod7YhF1weUtaXchVJGbNRBI7DUPwBeY2uJgsA7Bor4qb+XsCPzxzdenS8hxQax5z7FCBOY6a5SSQi4mryj30rTt0SjrN6KdIknnTYQks8GXeMaz7VlOrdF2CplNRXF+AGxS1CXxiqr/kuhAw4WFE="]
["send", 683498874159352, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AFS7AME8eQ7ekgBSkCMyEut7Gwn6MYQenzBIWdNgAio6brAJTddb2DqSjWPUS1NQ2Nh6wwipFd1KRLH/F3ruQ5/+dSXcoPlV4J8T0Jm8eqx0o6DayguwFAyu1N5yiZk5bl5GmxYlEFlUi0VPOMcPPzYnqNbsYIgOAO63XcfBHNJsA5vCNsaKnqAktWHtzXmXtZTvZgEHJGJD8LIhZ6lM8wd1Ui3zFlB4GCG7dWjgn13z+TSbgHHHyt1dnj7a/uNxMwrWrQgSnkL/tzrBL0xSwAiVuwtBZpGdab85icqUkQeL6OZdYk21Oz9MVjHuGPrXq3npNX76/wgP3+rAeHc+r1Pt/qEd+KHbG4G+OHUTlIHygGth57dUDM5rum5AdW3/8YTx+tAncedBybrAlikzDh+SYpztfAQG"]
["receive", 683498875161114, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADF0XwrZIgz9knLzOrJkDSf9kwXRYOEpfY5UfzmZM7/qdko="]
["receive", 683498875255833, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADJ0Xwr/eCKktmaP1Uc1+Zp5aNJM9lEmVebne+lDI+DNiP2XImSTH8ARBUcbF9HLDK1/4dT55AGOts4iZ0ZNOZFVXAru45pVapgT8bN8uxPYgQ=="]
["send", 683498875486027, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AFS7AMI8eQ4P2pPGapfOg39gOtTZgx8gbquT4vchxWodZJvZeTFpbBUvommihSJ/3+D7sTifa9gz+fM9rdyWF1rd4Cx7EyM="]
["receive", 683498875778780, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADN0XwoUf68e6LmehobmSAeEE7+bJ9hopGfmhjlTakDo8ug="]
["receive", 683498875892114, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADR0XwrxT37Olu9HyEQPuSGLVT3EjolLh8dB3hIb4YO/Q/vxakfp1Qs7D8fPhLUjI1jjph5HfD2ogeuQe1M="]
["send", 683498876146515, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AFS7AMM8eQ7tgujEMBfFaIlrN/xCl8Y4a2JH5UqEYxuRQeJHjMpRBm6WTBFnXXA5KuInASn40rPnidJjVMgnXJvaUeNbqwsM2tIu"]
["receive", 683498876473603, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADV0XwqZojBtHTLIdbFe56V1UIE4WaooRa60ax+r6P31Jhxj1n075g/a3tNUOEfyBkmQJSgUH0MQxSz84D1YFtaZAw=="]
["send", 683498876708616, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AFS7AMQ8eQ64jIF0YadOHTE9nqad5MkK1by0uhBSadiviVXlc03a1WLgBkySoX6214T1XIwpMTxWrZfVaV17z+S3jL2okeSrmdDE"]
["receive", 683498877025054, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADZ0Xwo9WH3ur9aGpocl4AKUtZkAFiAK87+d4cQqMjAHsUt5kTWAtvekb1R0w+/kYsC5QwVklDk4lvQ="]
["send", 683498877240551, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AFS7AMU8eQ5vFsc6CWDDdoCuIls1wkgctrxuFrnBzZRCtLNK9Qt3HRb1C6vSRNJ0pGU27NuN6l2k9mEHCy0WNhERx8oxUP39"]
["receive", 683498877583799, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADd0XwrDKFXAh2biGtXh+eG+a37SSK7iHHclvobcxFZJT3TFpKlMw5y75O8h1yrySPNt+RKjWKqx13c="]
["send", 683498877788546, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AFS7AMY8eQ6Is+Atiw2ABb7FL5iy1L6mM5g/k7414s1u2n/lLreAXNCJWDu+dhXcgeK8jXO+nGMOa+PDR8lpj4Lwrxi0MP/h"]
["receive", 683498878095526, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADh0Xwq1tLR8JSXMAh/4eBGVh9nvr6c7D5xraOqx3CTV4Usdv049V7JfXqJVAppEJvHMOXk+ZCUyObv60Q2+lgvaj+lGhA6W/b+5ztZVo8OJIcZ9Rll1FgrwTA=="]
["send", 683498878325730, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AFS7AMc8eQ4DBvpgfxkI8u77TH5UJBeFjkKJgW4W1SxT6Gfp2ZKvNNJOjEDmt/G/vWgzZlvcUq9jKCqQdTkn2MlT0aSwItbPLjJ1"]
["receive", 683498878665963, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AAEAADl0Xwoh7zyfoLawEVHK/jEfWmf+jEPDbe5cme3DeR/UBcQz1OhT4f8oHNQk6nPIt+Ss0mNHWS7Fapss/z0="]
["send", 683498878871692, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 60184, 0, 0], "AFS7AMg8eQ5q8qqBOCg+y9xQcPIrn3pfJVHbKBSkMMzzOtyYKIMD/ZK4O0TLX8EqzIdOtmdET05jajUN5yWSG6rWZqQcaKiygGUc"]
["urandom", 784666670915434, 4, "gTyCfg=="]
["urandom", 784666670932045, 4, "WEtNXg=="]
["urandom", 784666670944279, 4, "Im0D9Q=="]
["urandom", 784666670954137, 4, "q3+7MQ=="]
["urandom", 784666670967192, 8, "GdHmWrzZGms="]
["receive", 784671745464646, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "BAAAANR1RwbAzDLm0ZLOIQUgmg0AABUwASD/PukDdOXeqJ1IBdpHgqsgOMm3wxKzsceqrEXbVN2XnyUCvzMkAwAoBDUFJQH0ASUCLAElA6APJAQRJAULJgYAAAMBJAcBGBg="]
["urandom", 784671771475666, 32, "R9etlJW4WJ2hI+MXNURAUgqg0EAvyRTyMrcduh1jOSE="]
["urandom", 784671771496185, 4, "SqSOEg=="]
["send", 784671771593809, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AQAAAMoj6AfAzDLm0ZLOIQIhmg0AANR1RwYVMAEg/z7pA3Tl3qidSAXaR4KrIDjJt8MSs7HHqqxF21Tdl58wAiBH162UlbhYnaEj4xc1REBSCqDQQC/JFPIytx26HWM5ISQDATUEJQEQJzACIObgj9CEx2MyPagRHuoX1OB32N8u1aKUpNKjb4b854YkGBg="]
["receive", 784671777601546, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "BAAAANV1RwbAzDLm0ZLOIQUimg0AABUwAUEEEqRKQ9IIhbFzmFRT5DujfzWbge9+63W++o8ir/oUfxBKkLElBbBaT+n96w7pMk6pxi/JfmJwouj3RjZYy4O4MRg="]
["randbelow", 784671777703077, 115792089210356248762697446949407573529996955224135760342422259061068512044369, 26649162366983277512998315276067044341880723378171385531045348526206345387847]
["send", 784671787820468, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AQAAAMsj6AfAzDLm0ZLOIQIjmg0AANV1RwYVMAFBBMSdK7qIGwnaU3L8sov92kGKTqOhK56kBKAj55TYu3mZ9mLXCQrXPeZ/38c/YMxeIJ3EAfN/bOR6ec35cqwV0yAwAiAiuC1nE3Ubp++vlT/OYtqaqGoFdt/Usottde5xkgwMShg="]
["receive", 784671788261229, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "BAAAANZ1RwbAzDLm0ZLOIQUkmg0AABUwASCsVcEv3Eh8OPfsmxLISxCVu7qwCbMN7+LL3jXTvSIkkRg="]
["send", 784671788334538, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AQAAAMwj6AfAzDLm0ZLOIQJAmg0AANZ1RwYAAAAAAAAAAA=="]
["receive", 784671788501062, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJPvywjQvaLnorG7I/AQoh4IJIg9SEd3owBKpVZd2KNZwHF5nlhQ8BhHzr/wpTFRUXtHo0kznWwiReoKTEnjb2e0lKSaGoAP5UADc8o+/IaJl5rvUneuijc9G9HIpcH4faBYMPQiOustDPLjUyDGobn/WYmPs29V1fle1Mizu5Y="]
["send", 784671789179181, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AL8zAEbqKAGkyTFFEYXsJR7xYOlDV6nG3ES3MXEEYJHxjkfLv/u19GYfxnFvWDylvm+NbKnprXNebZiVdveov7t1fuaYovmxjv+nZ0RfQBkrabKLERGg5VKxeVoQxFYuIhqRj4iaXJAC3PQjmFA9Lrw6t3VNgcmG1yHa1fY5210krbhRPkxSPBFkbFxRDz+ige2uvRTzdANY1tI5ZhfiYYfC3LwaJsuuSuxhwIXbFoE/5qu8Zp/nAKGZme5PtrBhZcJDMOjKcsfK1Qgyn4kDTUiUMBSIBB2QGuoUf0db6R44fbcNlVvShYI="]
["receive", 784671790132390, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJTvywgs9CHUeFUMwzTra9wAscaZOCzTg6O62BGTYcVAuHo="]
["receive", 784671790226888, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJXvywiLBDIrxesC/ZX4yeRwt9gndvFwbE8ip2lWnOCxh1u0yv3FKPUVW6+0Ks1uqo6aOJ9FLvGHeyidHLhA2wL1yXSikgvl6s8PDVoniSPXSg=="]
["send", 784671790449899, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AL8zAEfqKAGsoFJIDBcl+c0GSsGQHaX5ZSkQLeprcL862VIIkH7a35ywtoJCtDFEOEJISHst2s0HB8NBkg=="]
["receive", 784671794339509, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJbvywiVXApjl2O2rCpEp6tCMdZsxXJ8Fg9hz1I3wbfwk3Y="]
["receive", 784671794460868, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJfvywhxaDeamkfBt6JM14rzvD6822cZ8/A2oFzI/B4cjRr+rVN4Xo6BJ/4XSWtexshQvDyDgAfufoyfaBs="]
["send", 784671794721990, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AL8zAEjqKAEuoNxQBRLD1VXDDfxtRJk9II/gIrTGAtPcUPEC4bSY+8oIfHmiCkjjl3r9E3ESLv8LaAMp4ncUqorFrA=="]
["receive", 784671795072291, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJjvywjZRYWm/kdLVbc/vtoKHymQDD1edYM1hHd3rU60iykpJB/Mr2hDRYYc9/zgJcqL/DlpqUaLEpXrJlEYN4teYA=="]
["send", 784671795307716, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AL8zAEnqKAHlAQA5+esg7SoD5Jcsx413iVa2qW5mPR765RLosKM15P7MhfPU6VISFU4N6Wi7ejm4wQp2bIlXPItFdg=="]
["receive", 784671795554070, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJnvywhA/bnhfPrxP7AHJa6AbuGIQ2/rp1frP/aWI5lqQp2y33YL++YHMZPV9cZ/jaPoFEXltq1LaAw="]
["send", 784671795904602, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AL8zAErqKAGDdc+1QQxQeLDWjhtSBX88F+bdVCnUkQ07c2psS1hMwTbbF592r6YncrDegkKmGp4o8gEHccr5MYAccNlKo+6GHx6m3i7mUOd9uYAFQ/8HylwXl+/2JoKGJdBjHQn34p322B4v6i7EEdr6t59W/UYSKz8cUtKr11rNPbI/w8u3teOHPmfCAo3WhOkjYnbBodSeoGvCX5Bwi1WB7xtyVVvcWZfm8d66Elq6ECjuWJk2bRUzfSuem6+l+kvwE6SbCEsUswz8Ny6LeUy9F0mISWFSzHxFKteKlFwjUUtV+WNVIqKPOlDCas39NkoyCl9+jBKc2CYdLQ2ifbrizGoQ3Q7hckAmpcl2Di/O0p/GOmOt/x9HIUnrPI6i3NlLKQYL9TVXwI9bUwGq5pbxtmP/TeiU3y2CEX8gEjrKMV7PcY7IsSuO2WQIHh2x/AT/dKdPYqQ1Il6tgQnnXJZ/5C9JYQBiBL1xmUTNKt4vmUtuW3uW7B/r8/ICkiZGyBpyvN+l+7lfRJGAs/riIbgFbUPdKrQfOpxvKFaD1S1HzD2Dc92X5f37j4tgBs4xerod9bBbIWRH+3aARmSHsoL8vPay86HHUAj+z6z1sI/ZyOGkThf9UDcIr0d3hKw0TltboG90Rtycrs0pOJTmZ2kKE5epzhbV3urjnVAESeeIAC94NR+Z9PeNkUq5djHMw8qdHsFukwQW"]
["receive", 784671796157078, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJrvywgWRzA9jTjmdG0PHw4W34ADp1z14TYE0VAdelkqjoH6pCdbjbk+Woe4GAkq8nuV/8Dv1qg25Ws="]
["send", 784671796492802, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AL8zAEvqKAGSmI/Hf2oUaTztMFwWf1Wr7UzcVAw00fbDY5I5JZv0GekEl89ttbbqxiqX01LOvqmaegYEyB5IR9xLTN2qV1DHvfLfVxxeRTh/w1RnVondC9DU+Tg2NDL+d0modgJy+mtrvANUaIjKVy/80J5LjG//NMP1aw61S3dr+eqp7ClE/rIFU3qwC4Gkk9M78XnzyTQTJ7Iwke6CmPkFaSAZRkCmRi1ps1x58InVKXdzz2h9Fj/9meP+BfJMgbQ57+XCTYyBKZsaDNyRevHdBXdVfFvYOARiPXtg5GiZQtGBBLsf8Ucrguo0CMKSxwfMWK82OPFoquGE8Dg23tqU4aCRBYK3Me6P94cU7QS/Ikg6zJmofCFbsS5VRjVljxxBrLcRRjC+40DUTkb20LR58421FZR8CbPUSAFoe7tG1nrg75OL4/4o2BGr5wC2s1XDfwq9noxvNNum+vQnRsdmS95yypSO+9QZp5Fy7dRQjLK6JMRRwwqUyN4AhnV8uOLhsZ+nhgBBmiJ0ZSV4yM3OYme11iuLFvoeeM2nVNXobfFXfXqItOui7Fwtm69rMUyXV1jEpPuWIJV5IVWTYp2XrtqhLVOgBgP7xte0dlZOalCcBXGqFLEwz5TYiTMVBpZmTYHX+bPrXnfpfGHMuuKt/TNtYYxbPFxhb6EwpDT2TYZQweh5mAvP5y+69WA5wuKaPlioGR4IE8vIbs9Pe8waBNb9YHH3jjP9UTEQ"]
["receive", 784671796763933, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJvvywhXIkAFnDC5PCH22NnVWxSNyPNJq7RYhdW518I67TbrGubnXwiOpqJ/spqjy31aVfGAyTGZb0yuROzcPBU9HCDtTNZ2PdygZwCgERNPgsFKu1bzgEUc3g=="]
["send", 784671797646439, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AL8zAEzqKAGZCnaekdC8CSrDLJFqRBq+EUvWc9/SlYjbcCyf4hxYXajISL9LOh6m1PAngaYvF/xaGwiTNMRULgp7NUYK/QeqcbQWQz9mKgDcgwspqG0PzqerpemTMSNfrqDF4LTWG6xjHkHjgp8xDWSXgfFF7sjtwz77VhLUIAKdiY2rmI0YzL3kXaaNr9qsJb5D4tVK2hfInfKN8gokigctcoR03gNm3gcaGlH3X39VgiD/PXQSY0rFQgKEndL9ifhvCK32PZDJnW1NgL/kpHmgnoUdgy5widWEaxj1Al+sRqEEKxJgpf7GIbmF7Wg+tMFPAC/2hq6LGjg/lplXvqezDc0mfbS/x+3B+N8lYPd+n56TQ3WnKjEJX4FsZe6vT8QHUqt8eUFVvMsQvNuPcn7ITCeM80qXF4lEXgX3aOOn79jp3kjj8dWD6z1PsG+QSNI3Mm3QtUW8QIDwbvD5Io0bme/Ui9qfXJwweYIfzMfyrNLORlo0sKqGwVMX/nOwvYxXnvq3NXOzwGbx9o1b0WO8vqa4SRKeTXJWeswQ"]
["receive", 784671799529853, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJzvywh6llkCvigUZMBX8vDqbW1YJEOmGfMR7PGU9XI7EgIR+yHPqksJ0VLs3Qc86cFy2ZC7PbyTC1pbNBI8dAoYbl8BC9TfygOTYx+YA1pJc7LEhLaKih2DUQ=="]
["send", 784671799737184, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AL8zAE3qKAEjEkjttuN4wJXi4DEWpedfLtARZkkDdIDPRcIGT2CGQCDJF+ekuQwRMsHcQqYo9bmq3PKGnelEUw=="]
["receive", 784671799972889, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AAEAAJ3vywjHE+kKkA4WvpHpLMVas8pnKJk/32kYtfqTDIpRK6GCg+WAoJYC29i5GwwqdR2YPbdFAhvn0UILgM0="]
["send", 784671800183566, ["fd98:bbab:bd61:8040:642:1aff:fe0c:9f2a", 34041, 0, 0], "AL8zAE7qKAFiLxKjMFToUcwYnxa0BIcm5HfRzM7T//WcWiyZO3OPshiawMakEDQLqN8V3wVakown/waF8LP65Iujuw=="]