간단 직렬화
페이지 마지막 업데이트됨: 2026년 2월 1일
간단 직렬화(SSZ)는 비콘 체인에서 사용되는 직렬화 방법입니다. 피어 검색 프로토콜을 제외하고 합의 레이어 전반의 실행 레이어에서 사용되는 RLP 직렬화를 대체합니다. RLP 직렬화에 대해 자세히 알아보려면 재귀 길이 접두사(RLP)를 참조하세요. SSZ는 결정론적이며 효율적으로 머클화되도록 설계되었습니다. SSZ는 직렬화 방식과 직렬화된 데이터 구조와 효율적으로 작동하도록 설계된 머클화 방식의 두 가지 구성 요소를 갖는 것으로 생각할 수 있습니다.
SSZ는 어떻게 작동하나요?
직렬화
SSZ는 자체 서술적이지 않은 직렬화 방식이며, 사전에 알려져야 하는 스키마에 의존합니다. SSZ 직렬화의 목표는 임의의 복잡성을 가진 객체를 바이트 문자열로 표현하는 것입니다. 이는 "기본 유형"에 대한 매우 간단한 프로세스입니다. 요소는 단순히 16진수 바이트로 변환됩니다. 기본 유형은 다음과 같습니다.
- 부호 없는 정수
- 불리언
복잡한 "복합" 유형의 경우, 복합 유형에 유형이나 크기가 다르거나 둘 다 다른 여러 요소가 포함되어 있으므로 직렬화가 더 복잡합니다. 이러한 객체가 모두 고정된 길이(즉, 요소의 크기가 실제 값에 관계없이 항상 일정함)를 갖는 경우 직렬화는 단순히 복합 유형의 각 요소를 리틀엔디언 바이트 문자열로 순서대로 변환하는 것입니다. 이러한 바이트 문자열은 함께 결합됩니다. 직렬화된 객체는 역직렬화된 객체에 나타나는 것과 동일한 순서로 고정 길이 요소의 바이트 목록 표현을 가집니다.
가변 길이 유형의 경우, 실제 데이터는 직렬화된 객체의 해당 요소 위치에 있는 "오프셋" 값으로 대체됩니다. 실제 데이터는 직렬화된 객체의 끝에 있는 힙에 추가됩니다. 오프셋 값은 힙에 있는 실제 데이터의 시작 인덱스로, 관련 바이트에 대한 포인터 역할을 합니다.
아래 예는 고정 및 가변 길이 요소를 모두 포함하는 컨테이너에 대한 오프셋 작동 방식을 보여줍니다.
12 struct Dummy {34 number1: u64,5 number2: u64,6 vector: Vec<u8>,7 number3: u648 }910 dummy = Dummy{1112 number1: 37,13 number2: 55,14 vector: vec![1,2,3,4],15 number3: 22,16 }1718 serialized = ssz.serialize(dummy)19모두 보기serialized는 다음과 같은 구조를 가집니다(여기서는 4비트로만 패딩되었지만 실제로는 32비트로 패딩되었으며, 명확성을 위해 int 표현을 유지합니다):
1[37, 0, 0, 0, 55, 0, 0, 0, 16, 0, 0, 0, 22, 0, 0, 0, 1, 2, 3, 4]2------------ ----------- ----------- ----------- ----------3 | | | | |4 number1 number2 vector의 number3 vector의5 오프셋 값6명확성을 위해 여러 줄로 나눴습니다:
1[2 37, 0, 0, 0, # `number1`의 리틀엔디언 인코딩.3 55, 0, 0, 0, # `number2`의 리틀엔디언 인코딩.4 16, 0, 0, 0, # `vector` 값의 시작 위치를 나타내는 "오프셋"(리틀엔디언 16).5 22, 0, 0, 0, # `number3`의 리틀엔디언 인코딩.6 1, 2, 3, 4, # `vector`의 실제 값.7]이것은 여전히 단순화된 것입니다. 위의 도식에 있는 정수와 0은 실제로는 다음과 같이 바이트 목록으로 저장됩니다.
1[2 10100101000000000000000000000000 # `number1`의 리틀엔디언 인코딩3 10110111000000000000000000000000 # `number2`의 리틀엔디언 인코딩.4 10010000000000000000000000000000 # `vector` 값의 시작 위치를 나타내는 "오프셋"(리틀엔디언 16).5 10010110000000000000000000000000 # `number3`의 리틀엔디언 인코딩.6 10000001100000101000001110000100 # `bytes` 필드의 실제 값.7]따라서 가변 길이 유형의 실제 값은 직렬화된 객체의 끝에 있는 힙에 저장되며, 오프셋은 정렬된 필드 목록의 올바른 위치에 저장됩니다.
BitList 유형과 같이 직렬화 중에 길이 상한을 추가하고 역직렬화 중에 제거해야 하는 특정 처리가 필요한 몇 가지 특수한 경우도 있습니다. 전체 세부 정보는 SSZ 사양 (opens in a new tab)에서 확인할 수 있습니다.
역직렬화
이 객체를 역직렬화하려면 스키마가 필요합니다. 스키마는 직렬화된 데이터의 정확한 레이아웃을 정의하여 각 특정 요소를 바이트 블롭에서 올바른 유형, 값, 크기 및 위치를 갖는 의미 있는 객체로 역직렬화할 수 있도록 합니다. 어떤 값이 실제 값이고 어떤 값이 오프셋인지를 역직렬화 프로그램에 알려주는 것이 바로 스키마입니다. 모든 필드 이름은 객체가 직렬화될 때 사라지지만, 스키마에 따라 역직렬화될 때 다시 인스턴스화됩니다.
이에 대한 대화형 설명은 ssz.dev (opens in a new tab)를 참조하세요.
머클화
그런 다음 이 SSZ 직렬화된 객체를 머클화할 수 있습니다. 즉, 동일한 데이터의 머클 트리 표현으로 변환할 수 있습니다. 먼저, 직렬화된 객체에서 32바이트 청크의 수를 결정합니다. 이것이 트리의 "리프"입니다. 리프를 함께 해싱하여 최종적으로 단일 해시 트리 루트를 생성할 수 있도록 총 리프 수는 2의 거듭제곱이어야 합니다. 자연스럽게 그렇지 않은 경우, 32바이트의 0을 포함하는 추가 리프가 추가됩니다. 도식적으로:
1 해시 트리 루트2 / \3 / \4 / \5 / \6 리프 1과 2의 해시 리프 3과 4의 해시7 / \ / \8 / \ / \9 / \ / \10 리프1 리프2 리프3 리프4모두 보기위의 예에서와 같이 트리의 리프가 자연스럽게 고르게 분포되지 않는 경우도 있습니다. 예를 들어, 리프 4는 머클 트리에 추가적인 "깊이"를 추가해야 하는 여러 요소를 가진 컨테이너일 수 있으며, 이로 인해 불균일한 트리가 생성됩니다.
이러한 트리 요소를 리프 X, 노드 X 등으로 지칭하는 대신, 루트 = 1에서 시작하여 각 레벨을 따라 왼쪽에서 오른쪽으로 계산하는 일반화된 인덱스를 부여할 수 있습니다. 이것이 위에서 설명한 일반화된 인덱스입니다. 직렬화된 목록의 각 요소는 2**depth + idx와 같은 일반화된 인덱스를 가집니다. 여기서 idx는 직렬화된 객체에서 0부터 시작하는 위치이고, 깊이는 머클 트리의 레벨 수이며, 요소(리프) 수의 밑이 2인 로그로 결정될 수 있습니다.
일반화된 인덱스
일반화된 인덱스는 이진 머클 트리의 노드를 나타내는 정수이며, 각 노드는 2 ** depth + 행의 인덱스라는 일반화된 인덱스를 가집니다.
1 1 --깊이 = 0 2**0 + 0 = 12 2 3 --깊이 = 1 2**1 + 0 = 2, 2**1+1 = 33 4 5 6 7 --깊이 = 2 2**2 + 0 = 4, 2**2 + 1 = 5...4이 표현은 머클 트리의 각 데이터 조각에 대한 노드 인덱스를 산출합니다.
다중 증명
특정 요소를 나타내는 일반화된 인덱스 목록을 제공하면 해시 트리 루트와 비교하여 이를 확인할 수 있습니다. 이 루트는 우리가 수용한 현실 버전입니다. 제공된 모든 데이터는 머클 트리의 올바른 위치(일반화된 인덱스에 의해 결정됨)에 삽입하고 루트가 일정하게 유지되는지 관찰함으로써 그 현실에 대해 검증될 수 있습니다. 사양에는 특정 일반화된 인덱스 세트의 내용을 확인하는 데 필요한 최소 노드 세트를 계산하는 방법을 보여주는 함수가 여기 (opens in a new tab)에 있습니다.
예를 들어, 아래 트리에서 인덱스 9의 데이터를 확인하려면 인덱스 8, 9, 5, 3, 1에 있는 데이터의 해시가 필요합니다. (8,9)의 해시는 해시 (4)와 같아야 하며, 이는 5와 해싱되어 2를 생성하고, 이는 3과 해싱되어 트리 루트 1을 생성합니다. 9에 대해 잘못된 데이터가 제공되면 루트가 변경됩니다. 우리는 이를 감지하고 브랜치 확인에 실패할 것입니다.
1* = 증명을 생성하는 데 필요한 데이터23 1*4 2 3*5 4 5* 6 768* 9* 10 11 12 13 14 157