B 확장과 스칼라 암호
기본 정수 ISA만으로도 모든 계산을 할 수 있다. 하지만 “비트를 한 칸 돌리기”, “1인 비트 세기”, “바이트 순서 뒤집기”처럼 흔한 일에 명령이 네다섯 개씩 들고, 앞쪽 0의 개수 세기는 루프까지 돌아야 한다. B 확장은 이런 자잘한 비트 작업을 명령 하나로 줄인다. 그 위에 스칼라 암호 확장(Zk*)은 AES·SHA-2·SM4·SM3의 핵심 단계를 하드웨어로 옮기고, 실행 시간이 비밀 데이터에 따라 달라지지 않는다는 약속까지 규격에 담았다. 이 장에서는 비트를 직접 눌러 보며 이 명령들을 하나씩 익힌다.
- B = Zba + Zbb + Zbs 라는 구성과, B에 들어가지 않는 Zbc·Zbk* 의 관계를 안다.
sh2add·add.uw같은 Zba 명령이 배열 주소 계산을 어떻게 줄이는지 명령 수로 비교한다.- Zbb 의 clz·ctz·cpop·rol·ror·rev8·orc.b·min·max 와 Zbs 의 단일 비트 명령을 비트 단위로 계산한다.
orc.b로 문자열의 끝(NUL 바이트)을 한 번에 여러 바이트씩 찾는 원리를 따라간다.- 캐리 없는 곱셈(clmul)이 보통 곱셈과 어떻게 다르고 CRC·GCM에 왜 쓰이는지 설명한다.
- Zkn·Zks·Zk 묶음, AES·SHA-2 보조 명령,
seedCSR(Zkr), 상수 시간 보장(Zkt)의 뜻을 안다.
왜 비트 조작 명령이 따로 필요한가
RV32I 에는 AND·OR·XOR 과 세 가지 시프트가 있다. 이것만 있으면 원리상 어떤 비트 연산이든 만들 수 있다. 문제는 비용이다. 32비트 값을 오른쪽으로 회전하려면 “반대 방향 시프트 양 구하기 → 왼쪽 시프트 → 오른쪽 시프트 → OR” 네 명령이 필요하고, 맨 위에서부터 0이 몇 개인지 세려면 루프를 돌거나 수십 개의 명령을 써야 한다. 컴파일러, 표준 라이브러리, 압축·암호·해시 코드, 운영체제의 비트맵 할당기에서 이런 패턴은 끝없이 반복된다.
| 하고 싶은 일 | 기본 정수 ISA로는 | 비트 조작 확장으로는 |
|---|---|---|
| a AND (NOT b) | not + and (2개) | andn (1개, Zbb) |
| 32비트 오른쪽 회전 | neg + sll + srl + or (4개) | ror (1개, Zbb) |
| 16비트 값 0 확장 | slli + srli (2개) | zext.h (1개, Zbb) |
| 앞쪽 0 개수 세기 | 루프 또는 이분 탐색 (10개 이상) | clz (1개, Zbb) |
| 1인 비트 개수 | 비트 트릭 10여 개 + 상수 적재 | cpop (1개, Zbb) |
| 배열 주소 base + i×4 | slli + add (2개) | sh2add (1개, Zba) |
| k번째 비트 세우기 | li + sll + or (3개) | bset (1개, Zbs) |
| GF(2) 다항식 곱셈 | 비트마다 시프트·XOR 루프 (100개 안팎) | clmul + clmulh (Zbc) |
그래서 RISC-V 는 비트 조작 명령을 용도별로 작은 확장 여러 개로 나눴다. 하나의 거대한 “B”를 만들려던 초안은 여러 해 논의 끝에 2021년, 꼭 필요한 것만 추린 네 확장(Zba·Zbb·Zbc·Zbs)으로 비준되었다. 같은 시기에 암호용 확장(Zbkb·Zbkc·Zbkx·Zk*)도 비준되었는데, 일부 명령은 두 쪽에 동시에 들어 있다. 명령의 인코딩은 어느 확장에서 오든 하나뿐이어서, 두 확장을 함께 구현해도 하드웨어가 겹치지 않는다.
clmulr을 뺀 것이다.“The B standard extension comprises instructions provided by the Zba, Zbb, and Zbs extensions.” … “The instructions have mnemonics and encodings that are independent of the extensions in which they appear. Thus, when implementing extensions with overlapping instructions, there is no redundancy in logic or encoding.”
Volume I, Bit Manipulation Extensions — 확장 이름은 “묶음표”일 뿐이고, 명령 하나하나의 인코딩은 유일하다.
| 확장 | 버전 | 내용 | 명령 수 (RV32 / RV64) |
|---|---|---|---|
| Zba | 1.0.0 | 시프트-덧셈 주소 계산, .uw 형 | 3 / 8 |
| Zbb | 1.0.0 | 부정 논리, 비트 세기, 최소·최대, 부호/0 확장, 회전, orc.b, rev8 | 18 / 24 |
| Zbs | 1.0.0 | 단일 비트 세우기·지우기·뒤집기·꺼내기 | 8 / 8 |
| Zbc | 1.0.0 | 캐리 없는 곱셈 (아래·위·뒤집힌 절반) | 3 / 3 |
| B | 1.0.0 | Zba + Zbb + Zbs 의 별칭 | — |
| Zbkb · Zbkc · Zbkx | 1.0.0 | 암호용 비트 조작·캐리 없는 곱셈·크로스바 치환 | — |
RV64 에서 점 없는 w 접미사(clzw, rolw …)는 기본 ISA 의 addw 와 같은 규칙을 따른다. 입력의 아래 32비트만 보고, 32비트 결과를 64비트로 부호 확장한다. .uw(unsigned word)는 한 피연산자의 아래 32비트를 0 확장해서 쓰고 나머지는 XLEN 전체로 계산한다. .b·.h·.w 는 아래 8·16·32비트만 보고 XLEN 폭 결과를 만든다는 뜻이다. 이 장의 시뮬레이터는 모두 RV32 기준으로 계산한다.
Zba: 배열 주소를 한 번에
C 의 a[i]는 기계어로 “시작 주소 + i × 원소 크기”다. 원소 크기는 대개 2·4·8바이트이니 곱셈 대신 왼쪽 시프트를 쓴다. 기본 ISA 로는 slli로 i 를 밀고 add로 시작 주소에 더하는 두 명령이 든다. Zba 의 sh1add·sh2add·sh3add는 이 둘을 하나로 합친다.
sh2add의 데이터 흐름. 2비트 왼쪽 시프트는 덧셈기 입력의 배선을 두 칸 옮기는 것일 뿐이라 논리가 거의 늘지 않는다. 규격이 시프트 양을 1·2·3으로만 한정한 까닭도 “덧셈기의 임계 경로를 늘리지 않기 위해서”다.세 명령은 인코딩도 가지런하다. 모두 R 형식, opcode 는 OP(0110011), funct7 은 0010000이고, funct3 만 다르다. funct3 의 위 두 비트를 읽으면 그대로 시프트 양이 된다.
sh2add.uw·sh3add.uw 도 같은 방식으로 OP-32 opcode 를 쓴다. OP-32 는 RV64 에만 있는 opcode 이므로 .uw 형은 RV64 전용이다..uw 형: RV64 에서 unsigned int 인덱스
RV64 에는 한 가지 함정이 더 있다. psABI 는 32비트 정수를 64비트 레지스터에 담을 때 부호 없는 타입이라도 부호 확장해 둔다고 정한다. 그래서 unsigned int i = 0x80000000 은 레지스터에 0xffffffff80000000으로 들어 있다. 이 값으로 주소를 계산하려면 먼저 위 32비트를 0으로 지워야 하는데, 기본 ISA 로는 slli 32 + srli 두 명령이 더 든다. add.uw·sh1add.uw~sh3add.uw·slli.uw는 “rs1 의 아래 32비트를 0 확장한 뒤” 계산하므로 이 정리 작업까지 한 명령에 흡수한다. zext.w rd, rs는 add.uw rd, rs, zero의 의사 명령이다.
Zba 배열 인덱싱: 명령 수 비교
기본 ISA
Zba 사용
slli.uw가 여전히 한 개를 아낀다).Zbb: 기본 비트 조작
Zbb 는 B 확장의 중심이다. 일곱 무리로 나뉘는데, 대부분이 “기본 ISA 로 2~4개, 많게는 수십 개 걸리던 관용구”를 한 명령으로 줄인 것이다.
| 명령 | 뜻 (RV32 기준) | 대표 쓰임 |
|---|---|---|
| andn · orn · xnor | rs1 & ~rs2 · rs1 | ~rs2 · ~(rs1 ^ rs2) | 마스크로 비트 지우기, 해시·암호의 논리 단계 |
| clz · ctz | 위·아래쪽부터 연속된 0의 개수 (입력이 0이면 XLEN) | log₂, 정규화, 비트맵에서 첫 빈칸 찾기 |
| cpop | 1인 비트의 개수 (population count) | 집합 크기, 해밍 거리, 체스 비트보드 |
| min · minu · max · maxu | 부호 있는/없는 비교로 작은·큰 값 | 분기 없는 범위 제한(clamp) |
| sext.b · sext.h · zext.h | 아래 8·16비트 부호 확장, 16비트 0 확장 | char·short 변환 |
| rol · ror · rori | 왼쪽·오른쪽 회전 (rol 의 즉시값형은 없다) | 해시, 암호, 난수 생성기 |
| orc.b | 바이트마다 하나라도 1이면 0xff, 아니면 0x00 | 문자열의 NUL 바이트 찾기 |
| rev8 | 레지스터 전체의 바이트 순서 뒤집기 | 엔디언 변환, 네트워크 바이트 순서 |
왼쪽으로 k 칸 회전하는 것은 오른쪽으로 XLEN−k 칸 회전하는 것과 같다. 시프트 양이 상수라면 어셈블러가 rori rd, rs, XLEN−k로 바꿔 쓰면 되므로 인코딩 공간을 아끼려고 roli를 두지 않았다.
부정 논리: 반전기를 다시 쓴다
andn·orn·xnor는 하드웨어 입장에서 거의 공짜다. 뺄셈 sub는 이미 rs2 를 반전해 더하는 회로를 갖고 있으므로, 그 반전기를 AND·OR·XOR 입력 앞에도 놓으면 된다. 인코딩도 sub와 같은 funct7(0100000)을 쓰고, funct3 은 각각 and(111)·or(110)·xor(100)와 같다.
회전: 밀려난 비트가 돌아온다
시프트는 밀려난 비트를 버리고 빈자리를 0(또는 부호 비트)으로 채운다. 회전은 밀려난 비트를 반대쪽 끝으로 다시 넣는다. 정보가 하나도 사라지지 않으므로 SHA-256, ChaCha20, SM3 같은 “더하기·회전·XOR(ARX)” 계열 알고리즘의 핵심 연산이다. 기본 ISA 로는 neg + sll + srl + or 네 명령이 들던 것을 규격은 바로 이 이유로 넣었다고 적고 있다.
0xB3을 한 칸 오른쪽으로 시프트할 때(위)와 회전할 때(아래). 금색 상자가 비트 0 이다. 실제 ror은 XLEN 폭 전체에서 같은 일을 하고, 회전 양은 rs2 의 아래 log₂(XLEN) 비트만 쓴다.rev8 과 orc.b: 바이트 단위로 보기
rev8은 레지스터의 바이트 순서를 통째로 뒤집는다. 리틀 엔디언 RISC-V 에서 빅 엔디언 데이터(네트워크 패킷 헤더, 암호 표준의 입력 블록)를 다룰 때 바로 쓰인다. 반쪽(16비트)이나 워드(RV64 의 32비트)만 뒤집고 싶으면 rev8 뒤에 srai(또는 srli)로 내려 주면 된다고 규격의 Software Hint 가 알려 준다. 인코딩은 RV32 와 RV64 에서 서로 다르다(즉시값 필드 0x698과 0x6b8).
rev8(RV32)의 동작. 바이트 k 가 바이트 3−k 자리로 옮겨 간다. 비트가 아니라 바이트 단위라서 각 바이트 안의 비트 순서는 그대로다. 바이트 안의 비트 순서까지 뒤집는 brev8은 Zbkb 에 있다.orc.b(OR-Combine, byte granule)는 각 바이트 안의 8비트를 OR 해서, 그 결과를 바이트 전체에 퍼뜨린다. 0인 바이트는 0x00, 0이 아닌 바이트는 0xff 가 된다. 별것 아닌 것 같지만 C 문자열의 끝(NUL)을 찾는 데 꼭 맞는 연산이다. 뒤의 strlen 절에서 자세히 본다.
clz · ctz · cpop 과 min · max
세는 명령은 입력이 0일 때의 결과가 분명히 정해져 있다. clz(0)과 ctz(0)은 XLEN 이고, 최상위 비트가 1이면 clz는 0이다. 그래서 31 − clz(x)는 0이 아닌 x 의 ⌊log₂ x⌋ 이고, ctz(x)는 비트맵에서 가장 낮은 번호의 1(예: 비어 있는 페이지 번호)을 바로 알려 준다. min·max는 분기 예측이 어려운 데이터 의존 분기를 없앤다. 예를 들어 x를 [lo, hi] 범위로 자르는 일은 max t0, a0, a1 + min a0, t0, a2 두 명령으로 끝난다.
Zbs: 비트 하나만 콕 집어
장치 레지스터의 플래그, 운영체제의 비트맵, 집합을 비트로 표현한 자료구조는 “i번째 비트 하나”를 다루는 일이 잦다. 기본 ISA 로는 li t0, 1 → sll t0, t0, a1로 마스크를 만든 뒤 OR·AND·XOR 를 해야 한다. Zbs 는 마스크 만들기와 연산을 한 명령에 묶는다.
0xB6의 비트 5 에 Zbs 네 명령을 적용한 결과(아래 8비트만 그림). 비트 위치는 rs2 의 아래 log₂(XLEN) 비트만 쓰므로 RV32 에서 rs2 = 37 도 비트 5 를 뜻한다. bext만 결과를 비트 0 으로 내려 0 또는 1 을 만든다.네 명령 모두 즉시값 형식(bseti·bclri·binvi·bexti)이 있다. 시프트 즉시값 명령처럼 shamt 필드에 비트 번호를 담으며, RV32 에서 shamt[5] = 1 인 인코딩은 예약되어 있다. 특히 bseti/bclri는 andi/ori의 12비트 즉시값이 닿지 않는 비트 11 이상을 한 명령으로 다룰 수 있다는 점이 쓸모 있다.
비트 놀이터: 직접 눌러 보기
지금까지 본 명령을 직접 계산해 보자. 아래 rs1(A)·rs2(B)의 비트를 눌러 바꾸거나 16진수를 입력하고, 명령을 고르면 rd 가 계산된다. 결과 비트 중 A 와 달라진 비트는 금색 테두리로 표시된다. 함께 나오는 기계어는 op a0, a1, a2(단항 명령은 op a0, a1)를 RV32 로 인코딩한 값이다.
비트 놀이터 (RV32)
0x12345678, B = 8 에서 ror과 rol을 비교해 보자. rd → A를 눌러 rev8을 두 번 적용하면 원래 값으로 돌아온다. B 를 37 로 두고 bset을 하면 비트 5 가 켜진다(아래 5비트만 쓰기 때문). clmul에서 A = B = 0xffffffff이면 왜 0x55555555가 나올지 생각해 보자.orc.b 로 strlen 빠르게
C 문자열의 길이를 구하는 가장 단순한 방법은 바이트를 하나씩 읽어 0인지 보는 것이다. 문자 하나마다 lbu · addi · bnez 세 명령이 든다. 레지스터에 4바이트(RV64 는 8바이트)를 한꺼번에 읽어 “이 안에 0인 바이트가 있나?”를 한 번에 물을 수 있다면 훨씬 빠를 것이다. orc.b가 바로 그 질문에 답한다. 0이 없는 덩어리는 orc.b 결과가 모두 1(= −1)이므로 beq 한 번으로 판정이 끝난다.
orc.b로 NUL 바이트를 찾는 원리(RV32, 리틀 엔디언). orc.b는 NUL 이 아닌 바이트를 0xff 로, NUL 바이트를 0x00 으로 만든다. not으로 뒤집으면 NUL 자리만 1이 되고, ctz가 오른쪽(낮은 주소)부터 0을 세어 첫 NUL 의 비트 위치를 준다. 8로 나누면(srli 3) 바이트 위치다.규격 부록의 strlen 예제는 이 원리 위에 시작 주소가 정렬되지 않은 경우의 처리(첫 덩어리를 시프트해 앞부분을 버림)를 더한 것이다. 핵심 루프는 단 네 명령이다.
orc.b 로 문자열 끝 찾기
규격 부록의 strcmp 예제는 두 문자열을 워드 단위로 비교하다가 다른 워드를 만나면 rev8로 두 워드를 빅 엔디언 순서로 뒤집은 뒤 sltu 한 번으로 크기를 비교한다. 바이트 순서를 뒤집으면 “앞 글자가 더 중요한” 사전식 비교가 정수 비교와 같아지기 때문이다.
Zbc: 캐리 없는 곱셈
보통 곱셈은 부분곱을 더한다. 같은 자리에서 1 + 1 이 나오면 윗자리로 올림(carry)이 생긴다. 캐리 없는 곱셈(carry-less multiplication)은 부분곱을 XOR 한다. 1 ⊕ 1 = 0 이고 올림은 없다. 이것은 계수가 0과 1뿐인 다항식, 곧 GF(2) 위의 다항식 곱셈과 정확히 같다. 비트 k 를 xᵏ 의 계수로 읽으면 된다.
XLEN 비트 두 수의 캐리 없는 곱은 최대 2·XLEN − 1 비트다. Zbc 는 이것을 세 가지로 잘라 준다.
| 명령 | 결과 (2·XLEN 비트 곱 P 에서) | 쓰임 |
|---|---|---|
| clmul | P[XLEN−1 : 0] — 아래 절반 | GCM 의 GHASH, CRC |
| clmulh | P[2·XLEN−1 : XLEN] — 위 절반 | clmul 과 짝을 이뤄 전체 곱 |
| clmulr | P[2·XLEN−2 : XLEN−1] — “뒤집힌” 절반 | 비트 순서가 반대인 CRC (Zbkc 에는 없음) |
clmulr의 r 은 reversed 다. 규격 비고에 따르면 이 명령은 “입력 둘을 비트 반전한 뒤 clmul을 하고 결과를 다시 비트 반전한 것”과 같다. 이더넷 CRC-32 처럼 비트를 LSB 부터 보내는 프로토콜의 CRC 를 계산할 때 비트 반전 없이 바로 쓸 수 있다.
보통 곱셈 vs 캐리 없는 곱셈 (8비트)
보통 곱셈: 부분곱을 더한다 (mul)
캐리 없는 곱셈: 부분곱을 XOR 한다 (clmul)
CRC 는 메시지를 GF(2) 다항식으로 보고 생성 다항식으로 나눈 나머지다. 바렛 축약(Barrett reduction)을 쓰면 이 나눗셈을 캐리 없는 곱셈 몇 번으로 바꿀 수 있어서, 비트마다 시프트·XOR 하던 루프나 큰 테이블 조회가 필요 없어진다. AES-GCM 의 인증 태그를 만드는 GHASH 는 GF(2¹²⁸) 의 곱셈이며, 128비트 곱셈을 64비트(또는 32비트) clmul/clmulh 조각으로 나눠 계산한다. 테이블 조회를 쓰지 않으니 캐시 타이밍으로 키가 새는 위험도 사라진다. 그래서 암호용 Zbkc 는 GHASH 에 필요한 clmul·clmulh만 담았다.
스칼라 암호 Zk*: AES · SHA-2 · SM4 · SM3
스칼라 암호 확장(v1.0.1)은 정수 레지스터만으로 표준 암호 알고리즘을 빠르고 안전하게 돌리기 위한 명령 모음이다. 벡터 유닛이 없는 작은 코어도 쓸 수 있도록 32비트·64비트 정수 레지스터 위에서 동작한다. 규격은 쓰임새에 따라 확장을 잘게 나눴다. TLS 1.3 의 GCM 모드처럼 복호화가 필요 없는 경우가 있어 AES 암호화(Zkne)와 복호화(Zknd)를 나눴고, 나라마다 요구하는 알고리즘이 달라 NIST 계열과 중국 ShangMi(SM) 계열도 나눴다.
AES: RV32 와 RV64 의 서로 다른 전략
AES 한 라운드는 SubBytes(S-box 치환) → ShiftRows(행 회전) → MixColumns(열 섞기) → AddRoundKey(키 XOR)로 이뤄진다. 128비트 상태를 레지스터에 어떻게 담느냐에 따라 두 가지 설계가 나왔다.
- RV32 (
aes32esi·aes32esmi·aes32dsi·aes32dsmi) — 바이트 하나씩 처리한다. 2비트 즉시값bs로 rs2 의 바이트 하나를 골라 S-box 에 통과시키고(…smi는 MixColumns 의 그 바이트 몫까지 계산),bs × 8만큼 회전해 rs1 에 XOR 한다. 소프트웨어의 “T-테이블” 방식을 하드웨어로 옮긴 것이다. - RV64 (
aes64es·aes64esm·aes64ds·aes64dsm·aes64im·aes64ks1i·aes64ks2) — 두 64비트 레지스터에 상태 전체(128비트)를 담고, 한 명령이 다음 라운드 상태의 절반(64비트)을 만든다. 같은 명령을 레지스터 순서만 바꿔 두 번 쓰면 한 라운드가 끝난다.
aes32esi rd, rs1, rs2, bs의 데이터 흐름(규격 Sail 코드를 그림으로 옮김). S-box 는 하드웨어 표 조회이지만, 규격은 이 명령의 실행 시간이 데이터에 따라 달라지면 안 된다고 못 박는다. 소프트웨어 표 조회가 캐시 타이밍으로 키를 흘리던 문제를 없애는 것이 목적이다.SHA-2: 시그마 함수를 한 명령으로
SHA-256 의 메시지 확장과 압축 단계는 회전 두세 번과 XOR 로 이뤄진 네 함수를 반복해서 부른다. Zbb 의 ror만 있어도 함수 하나에 회전 3 + XOR 2 = 5개쯤 드는 일을, Zknh 는 한 명령으로 줄인다.
| 명령 | 계산 (32비트) | SHA-256 표기 |
|---|---|---|
| sha256sig0 | ror(x,7) ⊕ ror(x,18) ⊕ (x ≫ 3) | σ₀ (메시지 확장) |
| sha256sig1 | ror(x,17) ⊕ ror(x,19) ⊕ (x ≫ 10) | σ₁ (메시지 확장) |
| sha256sum0 | ror(x,2) ⊕ ror(x,13) ⊕ ror(x,22) | Σ₀ (압축) |
| sha256sum1 | ror(x,6) ⊕ ror(x,11) ⊕ ror(x,25) | Σ₁ (압축) |
SHA-512 용으로는 RV64 에 같은 꼴의 64비트 명령 네 개(sha512sig0 등)가, RV32 에는 64비트 값을 두 레지스터로 나눠 계산하는 여섯 개(sha512sig0h/l, sha512sig1h/l, sha512sum0r, sha512sum1r)가 있다.
SM4 · SM3 과 Zbk* 보조 명령
중국 표준 블록 암호 SM4 는 암호화와 복호화가 같은 라운드 함수를 쓰므로 확장도 하나(Zksed: sm4ed, sm4ks)다. 구조는 RV32 AES 명령과 같다. bs로 바이트 하나를 골라 S-box 와 선형 변환을 거친 뒤 회전해 rs1 에 XOR 한다. 해시 SM3 용 Zksh 는 치환 함수 P0(x ⊕ rol(x,9) ⊕ rol(x,17))과 P1(x ⊕ rol(x,15) ⊕ rol(x,23))을 각각 한 명령으로 만든다.
암호용 비트 조작 확장은 이렇게 쓰인다. Zbkb 의 pack·packh는 바이트 문자열을 워드로 모으고, brev8은 GHASH 의 비트 순서를 맞추며, RV32 전용 zip·unzip은 SHA-3 의 64비트 회전을 32비트 레지스터 둘로 흉내 낼 때 쓴다. Zbkx 의 xperm4·xperm8은 레지스터 안에 담은 작은 표에서 4비트·8비트 원소를 골라 오는 “레지스터 속 표 조회”다. 메모리 표 조회 없이 S-box 를 구현할 수 있어 실행 시간이 비밀 값에 따라 달라지지 않는다. 규격은 이 방식으로 구현할 수 있는 8비트 S-box 의 예로 AES, SM4, 일본의 Camellia 와 함께 한국의 ARIA를 든다.
Zkr 엔트로피 소스와 Zkt 상수 시간
암호는 좋은 알고리즘만으로 완성되지 않는다. 예측할 수 없는 난수가 필요하고, 계산하는 동안 시간으로 비밀이 새지 않아야 한다. 스칼라 암호 확장의 마지막 두 조각이 이 둘을 맡는다.
seed CSR: 하드웨어 엔트로피 16비트씩
Zkr 은 비특권 CSR seed를 주소 0x015에 정의한다. 물리적 잡음원에서 모은 엔트로피를 한 번에 16비트씩 내준다. 이 값은 그대로 난수로 쓰기보다, SHA-2 등으로 정리(conditioning)한 뒤 AES 기반 CTR_DRBG 같은 결정적 난수 생성기의 씨앗(seed)으로 쓰라고 규격은 권한다.
seed CSR(0x015)의 비트 배치. 읽으면 엔트로피가 지워지는(wipe-on-read) CSR 이라서 “읽기와 쓰기를 함께 하는” csrrw rd, seed, x0로 읽는 것이 정석이다. OPST 가 ES16 이 아니면 entropy 필드는 0이어야 한다.seed의 OPST 상태 전이(규격 그림 “Entropy Source state transition diagram”을 옮김). 평소에는 WAIT(아직 없음)와 ES16(16비트 유효)을 오간다(금색 점). BIST 는 리셋 직후의 자체 시험이며, 동작 중에 BIST 로 돌아가면 치명적이지 않은 경보라는 뜻이다. DEAD 는 복구할 수 없는 오류다.seed는 기본적으로 M 모드에서만 접근할 수 있다. S·U 모드에서 쓰게 하려면 M 모드가 mseccfg CSR 의 SSEED·USEED 필드를 켜 줘야 하고, 그렇지 않으면 불법 명령 예외가 난다. 가상 머신 안의 운영체제에는 하이퍼바이저가 이 접근을 가로채 소프트웨어 난수 생성기(규격의 “가상 엔트로피 소스”, 최소 256비트 보안 수준)로 흉내 낸 값을 줄 수 있다.
Zkt: 실행 시간이 데이터에 따라 달라지지 않는다는 약속
타이밍 공격은 “계산이 얼마나 걸렸는지”만으로 비밀 키를 알아낸다. 곱셈기가 0을 만나면 일찍 끝난다거나, 비밀 값에 따라 분기 방향이나 메모리 주소가 바뀌면 시간 차이가 생긴다. 암호 라이브러리 개발자들은 그동안 “이 명령은 상수 시간일 것”이라고 가정하며 코드를 써 왔다. Zkt 는 그 가정을 규격의 약속으로 바꾼다.
| Zkt 목록에 있는 것 (구현했다면 데이터 독립 시간) | 목록에 없는 것 |
|---|---|
RVI 의 산술·논리·시프트·slt*, lui·auipc·addi 등 | load·store, 조건 분기 |
M 의 곱셈 (mul·mulh·mulhsu·mulhu·mulw) | 나눗셈·나머지 (div·rem …) |
C 의 해당 압축 명령, Zcb 의 c.mul·c.not·c.zext.b | 부동소수점 F·D·Q |
| Zbkb·Zbkc·Zbkx 전체, 모든 Zk* 암호 명령 | Zicsr·Zifencei, HINT 인코딩 |
“The Zkt extension attests that the machine has data-independent execution time for a safe subset of instructions. … Zkt does not define a set of instructions available in the core; it just restricts the behaviour of certain instructions if those are implemented.”
Volume I, Scalar Cryptography — Zkt — Zkt 는 새 명령이 아니라 기존 명령의 “시간적 성질”에 대한 약속이다. 그래서 곱셈기가 없는 단순한 RV32I 코어도 Zkt 를 만족할 수 있다.
Zkt 는 명령 하나의 실행 시간만 약속한다. 비밀 값으로 분기하거나 비밀 값을 주소로 메모리를 읽는 코드는 여전히 위험하다(그래서 load·store·분기가 목록에서 빠져 있다). 스펙터(Spectre)류의 추측 실행 공격도 Zkt 의 범위 밖이라고 규격이 분명히 적고 있다. 비순차 코어가 명령을 합치거나 쪼개는 최적화는 허용되지만, 그 결정이 피연산자 값에 따라 달라져서는 안 된다.
핵심 정리
- B = Zba + Zbb + Zbs(v1.0.0)다. 캐리 없는 곱셈 Zbc 는 B 에 들어가지 않는다. 명령의 인코딩은 어느 확장에서 오든 유일하다.
- Zba의
sh1add/sh2add/sh3add는rd = rs2 + (rs1 ≪ n)로 배열 주소 계산을 한 명령에 끝낸다. RV64 의.uw형은 32비트 인덱스의 0 확장까지 흡수한다. - Zbb는 부정 논리, clz·ctz·cpop, min·max, 부호/0 확장, 회전,
orc.b,rev8을 담는다. clz·ctz 는 입력이 0이면 XLEN 을 돌려준다. - Zbs의 bset·bclr·binv·bext 는 rs2(또는 즉시값)의 아래 log₂(XLEN) 비트로 비트 하나를 고른다.
orc.b+not+ctz로 여러 바이트 중 첫 NUL 의 위치를 한 번에 찾는다. strlen 의 핵심 루프는 네 명령이다.- clmul/clmulh/clmulr는 부분곱을 XOR 하는 GF(2) 다항식 곱셈이다. CRC 와 AES-GCM 의 GHASH 를 표 없이 빠르게 계산한다.
- 스칼라 암호(v1.0.1)는 Zk = Zkn + Zkr + Zkt, Zkn = Zbkb+Zbkc+Zbkx+Zkne+Zknd+Zknh, Zks = Zbkb+Zbkc+Zbkx+Zksed+Zksh 로 묶인다.
seedCSR(0x015)은 OPST(BIST·WAIT·ES16·DEAD)와 16비트 엔트로피를 준다. Zkt 는 목록의 명령이 데이터와 무관한 시간에 실행된다는 약속이다.
확인 퀴즈
“B” 확장을 지원한다고 할 때 반드시 들어 있는 확장의 조합은?
sh2add a0, a1, a2를 실행한 뒤 a0 의 값은?
RV32 에서 orc.b를 0x00410042에 적용한 결과는?
RV64 에서 unsigned int 인덱스로 배열에 접근할 때 sh2add.uw가 기본 ISA 보다 특히 유리한 이유는?
clmul로 3(0b11)과 3(0b11)을 곱한 결과는?
csrrw a0, seed, x0의 결과가 0x8000ABCD였다. 올바른 해석은?
Zkt 확장에 대한 설명으로 옳은 것은?
RV64 의 aes64esm t2, t0, t1에 대한 설명으로 옳은 것은?
aes64esm t3, t1, t0로 만든다. MixColumns 가 없는 마지막 라운드용은 aes64es다.더 읽을거리
- The RISC-V Instruction Set Manual, Volume I — Bit Manipulation Extensions (B, Zba, Zbb, Zbs, Zbc, Zbkb, Zbkc, Zbkx) (v20260120)
- Volume I — Bit Manipulation Extensions Assembly Code Examples (strlen, strcmp)
- Volume I — Cryptography Extensions: Scalar & Entropy Source Instructions, Version 1.0.1
- Volume I — Vector Cryptography Extensions (벡터 쪽 대응 확장, 15장 참고)
- RVA23 Profiles — B·Zkt 필수, Zbc 확장 옵션
- Ben Marshall 외, “The design of scalar AES Instruction Set Extensions for RISC-V”, IACR TCHES 2021(1) — RV32·RV64 AES 명령의 설계 공간 탐색
- NIST FIPS 197 (AES), FIPS 180-4 (SHA-2), SP 800-38D (GCM), SP 800-90B (엔트로피 소스)