[Aura Core Architecture #6] Aura Binary Parser: C 라이브러리 없는 순수 함수형 CBOR 역직렬화와 비결정론적 패킷 공격 방어
Published: 2026-08-09
Author: Joy & Hope Stake Pool / Aura Core Architecture Team
Category: Blockchain Architecture / Pure Functional Programming
Tags: Haskell, Cardano, CBOR, BinaryParser, P2P, TypeSafety, Aura
1. 서론: 악성 바이너리 패킷과 P2P 네트워크의 취약성
탈중앙화 P2P 네트워크의 모든 검증 노드는 전 세계의 불특정 피어(Peer)로부터 초당 수천 개의 바이너리 패킷을 수신합니다. 트랜잭션 서명이나 스마트 컨트랙트 로직이 실행되기도 전에 가장 먼저 실행되는 코드는 바로 바이너리 역직렬화(Deserialization) 파서입니다.
역사적으로 블록체인 P2P 계층에서 발생한 심각한 장애는 다음 두 가지 직렬화 결함에서 기인했습니다:
- 메모리 버퍼 취약점: C/C++ 기반 파서(
libcbor,protobuf등)가 길이 헤더(Length Header)와 실제 페이로드 크기의 불일치를 검증하지 못해 발생하는 버퍼 오버리드(Buffer Over-read) 및 원격 코드 실행(RCE). - 비결정론적 인코딩에 의한 합의 분열(Malleability): 동일한 데이터 구조체가 파서의 구현 차이나 맵(Map) 키의 정렬 방식에 따라 서로 다른 바이트 배열로 직렬화되는 현상. 이는 동일한 트랜잭션에 서로 다른 트랜잭션 ID(TxID)가 부여되는 트랜잭션 가변성(Transaction Malleability) 공격을 유발합니다.
5세대 블록체인 Aura는 외부 C 바인딩을 일체 배제하고, 수학적으로 안전한 모나딕 파서 콤비네이터(Monadic Parser Combinator) 모델을 기반으로 순수 하스켈 결정론적 CBOR 파서를 설계했습니다.
2. 결정론적 CBOR (RFC 8949)과 카르다노 직렬화 표준
카르다노와 Aura는 트랜잭션 및 블록 데이터 직렬화 표준으로 CBOR(Concise Binary Object Representation, RFC 8949)을 채택하고 있으며, 엄격한 표준형(Canonical) 규칙을 강제합니다.
CBOR의 각 데이터 항목은 첫 번째 바이트(Initial Byte)의 메이저 타입(Major Type, 상위 3비트)과 부가 정보(Additional Information, 하위 5비트)로 구성됩니다:
graph TD
Byte["CBOR 초기 바이트 (Initial Byte) [8 Bits]"]
Major["상위 3비트: Major Type (0..7)"]
Info["하위 5비트: Additional Info (0..31)"]
Byte --> Major
Byte --> Info
Major --> M0["0: 양의 정수 (Unsigned Int)"]
Major --> M1["1: 음의 정수 (Negative Int)"]
Major --> M2["2: 바이트 문자열 (Byte String)"]
Major --> M3["3: 텍스트 문자열 (UTF-8 String)"]
Major --> M4["4: 배열 (Array)"]
Major --> M5["5: 맵 (Map / Key-Value)"]
Major --> M6["6: 시맨틱 태그 (Tag)"]
Major --> M7["7: 단순 값 / 부동소수점 (Prims)"]
2.1. 표준 정규형(Canonical CBOR) 불변 규칙
Aura 원장은 동일한 데이터에 대해 오직 단 하나의 유일한 바이트열(Bijective Mapping)만을 인정합니다:
1. 정수 최소 인코딩(Shortest Form): 값 $10$을 표현할 때 1바이트(0x0a)로 충분함에도 2바이트(0x180a)나 4바이트로 인코딩하는 가변 패킷은 즉시 거부.
2. 사전식 키 정렬(Lexicographical Key Ordering): 맵(Map)의 모든 키는 바이트 단위 사전식 순서로 정렬되어야 하며, 중복 키(Duplicate Key)는 즉시 파싱 실패 처리.
3. 가변 길이(Indefinite-Length) 인코딩 금지: 스트리밍용 미확정 길이 청크를 원장에 직접 기록하는 행위를 금지하여 TxID 조작 방지.
3. 순수 상태 전이 모나딕 파서 설계 (Parser.hs)
C 라이브러리의 memcpy나 포인터 직접 연산 대신, Aura 바이너리 파서는 상태 모나드(State)와 실패 모나드(Either)를 결합한 순수 함수형 역직렬화기로 동작합니다.
-- | 순수 불변 바이트 스트림 파서 타입
newtype Parser a = Parser { runParser :: [Word8] -> Either ParseError (a, [Word8]) }
-- | 파싱 실패 범주 명시 (Fail-Safe ADT)
data ParseError
= UnexpectedEndOfInput
| InvalidMajorType Word8
| NonCanonicalEncoding String
| MalformedUtf8
| DuplicateMapKey
deriving (Show, Eq)
instance Functor Parser where
fmap f (Parser p) = Parser $ \bs -> case p bs of
Left err -> Left err
Right (a, rest) -> Right (f a, rest)
instance Applicative Parser where
pure a = Parser $ \bs -> Right (a, bs)
(Parser pf) <*> (Parser pa) = Parser $ \bs -> do
(f, bs') <- pf bs
(a, bs'') <- pa bs'
Right (f a, bs'')
instance Monad Parser where
(Parser p) >>= f = Parser $ \bs -> do
(a, rest) <- p bs
runParser (f a) rest
이 구조에서는 입력 버퍼의 경계를 초과하는 순간 UnexpectedEndOfInput이 정적 에러로 반환될 뿐, 어떠한 세그멘테이션 오류(Segmentation Fault)나 버퍼 오버런(Buffer Overrun)도 물리적으로 일어날 수 없습니다.
4. CBOR 핵심 원시 타입 디코딩 구현체
4.1. 단일 바이트 및 가변 정수(Unsigned Int) 파싱
import Data.Bits (shiftR, (.&.), shiftL)
import Data.Word (Word8, Word64)
-- | 1바이트 안전 추출
getWord8 :: Parser Word8
getWord8 = Parser $ \case
[] -> Left UnexpectedEndOfInput
(b:bs) -> Right (b, bs)
-- | N 바이트 정확 슬라이스 (Zero-Copy 안전 보장)
getBytes :: Int -> Parser [Word8]
getBytes n = Parser $ \bs ->
if length bs < n
then Left UnexpectedEndOfInput
else Right (splitAt n bs)
-- | Major Type 0: 정규형 양의 정수 디코더
parseUInt :: Parser Word64
parseUInt = do
b <- getWord8
let major = b `shiftR` 5
info = b .&. 0x1f
if major /= 0
then Parser $ \_ -> Left (InvalidMajorType major)
else case info of
val | val < 24 -> pure (fromIntegral val)
24 -> do -- 1바이트 추가 정수 (값 >= 24)
val <- fmap fromIntegral getWord8
if val < 24
then Parser $ \_ -> Left (NonCanonicalEncoding "1바이트 확장 정수는 24 이상이어야 합니다.")
else pure val
25 -> do -- 2바이트 Big-Endian 정수 (값 >= 256)
[b0, b1] <- getBytes 2
let val = (fromIntegral b0 `shiftL` 8) + fromIntegral b1
if val < 256
then Parser $ \_ -> Left (NonCanonicalEncoding "2바이트 확장 정수는 256 이상이어야 합니다.")
else pure val
_ -> Parser $ \_ -> Left (NonCanonicalEncoding "지원되지 않는 비정규 정수 헤더입니다.")
비표준 인코딩(예: $10$을 2바이트로 부풀려 전달)이 유입되면 파서는 즉각 NonCanonicalEncoding 에러를 반환하며 연결을 격리합니다.
5. 트랜잭션 가변성(Malleability) 방어 시뮬레이션
공격자가 P2P 네트워크에 서명은 유효하지만 CBOR 바이트열을 변형한 동일 트랜잭션을 살포하는 시나리오를 방어합니다:
sequenceDiagram
autonumber
actor Attacker as 악의적 피어 (Attacker)
participant Parser as Aura Pure CBOR Parser
participant Mempool as 노드 멤풀 (Mempool)
Attacker->>Parser: 비표준 패킷 전송 (Shortest Form 위반: 0x180a)
Note over Parser: parseUInt 검사:<br/>info == 24 && val < 24
Parser--xAttacker: ParseError (NonCanonicalEncoding)
Note over Parser: 런타임 패닉 0%, 즉시 패킷 폐기
Parser->>Mempool: 오염된 트랜잭션 침투 원천 차단
단위 테스트 검증 결과:
-- 정규형 패킷 (0x0a: 정수 10)
runParser parseUInt [0x0a]
-- 결과: Right (10, []) -> 성공!
-- 가변 공격 패킷 (0x18, 0x0a: 비표준 2바이트 10)
runParser parseUInt [0x18, 0x0a]
-- 결과: Left (NonCanonicalEncoding "...") -> 안전한 차단!
6. 결론: 기계 간(M2M) P2P 메시징의 견고한 방패
Aura Binary Parser는 C 라이브러리의 보안 결함과 비결정론적 직렬화 공격으로부터 분산원장을 보호하는 근본적인 방패입니다:
- RCE / 오버런 100% 면역: 순수 하스켈 타입 시스템과 리스트 패턴 매칭으로 메모리 조작 공격 원천 차단.
- 트랜잭션 가변성 제로: RFC 8949 Canonical 규약을 엄격히 강제하여 동일 트랜잭션의 중복 등록 및 이중 지불 방지.
- WASM & 임베디드 즉시 호환: 외부 바이너리 바인딩 없이 모든 엣지 노드와 브라우저 환경에서 동일한 바이트 역직렬화 보장.
이로써 Aura 5세대 블록체인은 암호학 해시(Blake2b) ➔ 화폐 원장(Coin) ➔ 주소 검증(Validator) ➔ 경량 상태 증명(Merkle) ➔ 패킷 역직렬화(CBOR)로 이어지는 완전무결한 5대 순수 함수형 코어 스택을 구축하였습니다.