내장 스크립트 개요
내장 스크립트 언어는 Script 노드 안에서 bytes, int, bool, string 값에 대한 데이터 의존적 연결 논리(조건 분기, 유한 반복, 사용자 정의 프레이밍, 보조 함수)를 표현하기 위해 설계된 결정적·정적 타입 Python 부분집합 언어이다.
암호 기본 연산은 전용 암호·해시·MAC·KDF·서명 노드가 담당하고, Script 노드는 그 사이의 바이트 단위 가공과 연결을 담당한다. 범용 Python 런타임이 아닌 NodeCrypto 자체 인터프리터가 실행하므로 웹앱, 데스크톱 앱, CLI, API Container 어디서나 완전히 동일하고 재현 가능한 결과를 보장한다.
설계 원칙
- 순수 함수(Pure Function): 스크립트는 입력 포트에서 값을 읽어 출력 포트에 내보낼 값을 계산한다. 파일·네트워크 입출력, 시각 조회, 난수 생성, 변경 가능한 전역 상태가 일절 없다. 무작위 바이트가 필요할 때는 Random Value 노드를 Script 입력 포트에 연결한다.
- 닫힌 세계(Closed World):
import문, 속성 접근(.), 동적 코드 평가가 없다. 스크립트 안에서 정의한 식별자와 내장 함수만 존재한다. - 정적 타입(Statically Typed): 실행 전에 모든 식의 타입을 결정한다. 문법 오류, 타입 불일치, 출력 포트 미대입 경로는 노드 속성을 저장할 때 즉시 검출하여 차단한다.
- 종료 보장(Guaranteed Termination):
while반복문과 재귀 호출이 없다. 모든 반복문은 엄격한 스텝 및 크기 한도 아래 유한한 리스트,bytes버퍼 또는 유한한range(...)만 순회한다.
Script 노드 구성 요소
| 구성 요소 | 설명 |
|---|---|
| 입력 포트 | 각 입력 포트는 고유 식별자(id / 표시 이름)와 정적 데이터 타입(bytes, int, bool, string)을 가진다. 추가된 모든 입력 포트는 필수 입력이다. |
| 출력 포트 | 0개 이상의 출력 포트를 둘 수 있으며, 각 포트는 고유 식별자와 정적 데이터 타입(bytes, int, bool, string)을 가진다. |
| 코드 | 스크립트 소스 코드 (최대 64 KiB). |
포트 이름이 스크립트 내부에서 변수 이름으로 직접 사용되므로, 모든 포트 이름은 식별자 규칙을 따라야 하며 예약어나 내장 함수 이름과 충돌할 수 없고 노드 내 모든 포트 통틀어 유일해야 한다. Script 노드에는 any나 set 타입 포트를 둘 수 없다.
입력·출력 포트 바인딩
- 각 입력 포트는 동일한 이름과 타입을 가진 읽기 전용 최상위 변수로 미리 바인딩된다. 입력 변수에는 값을 재대입할 수 없다.
- 출력 포트 이름과 일치하는 최상위 변수에 값을 대입하면 해당 출력 포트의 결과값이 설정된다. 대입하는 값의 타입은 출력 포트에 선언된 데이터 타입과 일치해야 한다.
- 스크립트 실행이 끝나는 시점에 실패하지 않는 모든 실행 경로에서 모든 출력 포트 변수가 반드시 확정 대입(Definitely Assigned)되어 있어야 한다. 어느 한 경로라도 출력 변수를 대입하지 않고 끝날 수 있으면 저장이 차단된다(확정 대입 검사 참고).
- 사용자 정의 함수 안에서는 입력 포트 변수를 직접 읽거나 출력 포트 변수에 직접 대입할 수 없다. 함수 인자로 값을 전달하고 최상위에서 반환값을 받아 출력 변수에 대입한다.
실행 수명주기
- 연결된 모든 입력 포트에 값이 준비되면 Script 노드가 실행된다.
- 최상위 문장들이 위에서 아래로 순차 실행된다.
- 실행이 정상적으로 끝에 도달하면 출력 변수들에 담긴 값이 대응하는 출력 포트로 방출된다.
fail(code, message)가 호출되거나 런타임 에러가 발생하거나 자원 한도를 초과하면 실행이 즉시 중단되며, 부분 출력은 방출되지 않고 노드는ERROR상태가 된다(에러 및 자원 한도 참고).
코드 편집기 사용
Script 노드 속성 창의 첫 번째 탭은 코드 편집기이며, 입력·출력 포트는 Ports 탭(또는 노드 카드)에서 관리한다. 정적 검사에서 오류가 발견되면 편집기가 해당 줄:열 위치와 진단 메시지를 표시하며 오류가 해결될 때까지 저장을 막는다.
검증된 예시
아래의 모든 예시는 내장 스크립트 평가기로 실제 실행하여 검증된 코드이다. 바이트 입출력은 16진수로 표기한다.
CMAC 서브키 파생 (조건 분기)
입력 l: bytes(16바이트 영 블록의 AES 암호화 결과), 출력 k1: bytes 및 k2: bytes. 입력이 7df76b0c1ab899b33e42f047b91b546f일 때 k1은 fbeed618357133667c85e08f7236a8de, k2는 f7ddac306ae266ccf90bc11ee46d513b이다(RFC 4493 테스트 벡터).
def dbl(x: bytes) -> bytes:
rb = h"00" * (len(x) - 1) + (h"87" if len(x) == 16 else h"1b")
r = x << 1
if x[0] & 0x80 != 0:
r ^= rb
return r
k1 = dbl(l)
k2 = dbl(k1)
카운터 블록 시퀀스 생성 (리스트와 반복문)
입력 seed: bytes와 n: int, 출력 out: bytes. seed가 aa이고 n이 3일 때 aa00000000, aa00000001, aa00000002를 이어 붙인 바이트열을 만든다.
parts: list[bytes] = []
for i in range(n):
parts += [seed + to_bytes(i, 4)]
out = concat(parts)
길이 검증 및 튜플 언패킹
입력 data: bytes, 출력 head: bytes와 body: bytes. 앞 4바이트를 헤더로, 나머지를 본문으로 분리한다. data가 0102030405이면 head는 01020304, body는 05이다.
def split2(b: bytes, n: int) -> tuple[bytes, bytes]:
if len(b) < n:
fail("DATA_TOO_SHORT", "data is shorter than the header")
return b[:n], b[n:]
head, body = split2(data, 4)
같은 스크립트에 1바이트 입력 01을 전달하면 DATA_TOO_SHORT 에러가 발생하며 노드가 에러 상태로 전환된다:
def split2(b: bytes, n: int) -> tuple[bytes, bytes]:
if len(b) < n:
fail("DATA_TOO_SHORT", "data is shorter than the header")
return b[:n], b[n:]
head, body = split2(data, 4)
DES 키 홀수 패리티 보정 (바이트 순회)
입력 key: bytes, 출력 fixed: bytes. 각 바이트의 1의 개수가 홀수가 되도록 최하위 비트를 조정한다. 입력 0022ff00에 대해 0123fe01을 출력한다.
out: list[int] = []
for v in key:
ones = 0
for s in range(1, 8):
ones += (v >> s) & 1
out += [(v & 0xFE) | (1 - ones % 2)]
fixed = from_ints(out)
서식 문자열(f-string)을 활용한 사용자 정의 검증 에러
입력 key: bytes, 출력 ok: bytes. key가 16바이트가 아니면 상세 에러 메시지와 함께 실행을 중단한다.
if len(key) != 16:
fail("BAD_KEY_LENGTH", f"key must be 16 bytes, got {len(key)} ({key:x})")
ok = key
상세 문서
- 문법: 들여쓰기, 리터럴, 문장, 함수, 인덱싱·슬라이싱, 반복문, f-string 및 Python과의 차이점.
- 타입 및 연산자: 타입 시스템 규칙, 확정 대입 검사, 값 의미론 및 연산자 표.
- 내장 함수: 24종 내장 함수의 시그니처와 검증된 실행 예시.
- 에러 및 자원 한도: 컴파일 시점 진단 코드, 런타임 에러 코드 및 실행 한도.