1️⃣

슈노르 서명

ECDSA의 한계

서명의 가변성 문제
같은 메시지에 대한 서명값은 항상 두 개 (s, N-s)가 존재한다.
N-s 는 모듈러 연산에서 -s와 같다. (N=11, s=3 인 경우 8과 11-3은 같다)
따라서 우리가 알던 서명 검증식은
u=zs, v=rsu=\frac{z}{s}, ~v=\frac{r}{s}
uG+vP=RuG+vP=R
이렇게 바뀐다.
u=zs, v=rsu'=\frac{z}{-s}, ~v'=\frac{r}{-s}
uGvP=R-uG-vP=-R
하지만 우리는 R의 x좌표만 서명으로 사용하기 때문에 R과 -R을 구분할 수 없다.
개인키를 잘 모른다 하더라도 누구든 유효한 서명값을 만들어낼 수 있다.
서명의 가변길이 문제
ECDSA의 서명 길이는 r, s 작은 수가 나오는 경우 패딩(0 채우기)를 강제하지 않는다.
30 06 ; SEQUENCE, length = 6 02 01 01 ; r = 1 (1 byte) 02 01 01 ; s = 1 (1 byte)
Python
복사
따라서 r, s의 길이를 표현하는 것이 필요하다.
서명이 없는 상태에서 수수료율 예측이 어렵다.
다중서명의 프라이버시 문제
ECDSA를 사용하면 다중서명 주소라는 것 자체가 노출된다.
P2WPKH 는 20바이트이고, P2WSH는 32바이트이다.
k (임시개인키)의 취약함 (BIP-340 적용)
k에 어떤 수나 사용할 수 있게 됨으로써, 어떤 수를 선택하느냐에 따라 안전하지 않을 수 있다.
비선형구조의 한계
선형구조면 f(d₁) + f(d₂) = f(d₁ + d₂)를 만족해야 한다.
ECDSA의 서명
s=z+reks=\frac{z+re}{k}
두 서명의 합은 부분들의 합으로 표현이 어렵다.
s1+s2=z+r1e1k1+z+r2e2k2=k1(z+r1e1)+k1(z+r2e2)s_1 + s_2=\frac{z+r_1e_1}{k_1} + \frac{z+r_2e_2}{k_2} = k^{-1}(z+r_1e_1) + k^{-1}(z+r_2e_2) 에서
일단 k11+k21(k1+k2)1k_1^{-1}+k_2^{-1}\neq(k_1+k_2)^{-1}가 성립하지 않음
따라서 두 개 이상의 서명을 묶어서 처리할 수 없다. (예: 다중서명)

슈노르 서명의 해결책

서명의 가변성 문제
슈노르 서명의 검증 식은 다음과 같다.
sG=R+ePsG = R + eP
여기서 s를 N-s 로 바꾸면 검증에 실패한다.
r의 y좌표를 항상 짝수로 유지한다. (만약 홀수가 추출되면 k를 N-k로 바꾼다)
서명의 가변 길이 문제
서명의 길이를 r, s 각각 32바이트로 고정한다.
만약 길이가 짧은 경우 패딩(0을 채움)을 한다.
r (Rₓ): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 (32 bytes)
Python
복사
별도의 길이 표현이 필요하지 않다.
다중서명 프라이버시의 문제
탭루트의 주소는 변형 공개키를 활용하며 다중 서명은 여러 공개키를 선형으로 조합하여 사용하므로 단일 서명과 길이가 같다.
탭루트는 공개키의 해시가 아닌 공개키를 그대로 쓴다. → 그럼 p2pkh에서 공개키를 UTXO에서 감추는 노력이 무의미해질까?
무의미해졌다. 그러나 커뮤니티는 이렇게 판단했다.
공개키 해시를 통해 UTXO 단계에서 공개키를 은닉하는 전략은, Taproot 도입 시점의 위협모델 재평가 결과 보안상 결정적 이점을 제공하지 않는 것으로 판단되었다.
P2PKH 설계 당시에는 공개키 장기 노출에 대한 암호학적 불확실성이 컸으나, 장기간의 실전 운영 결과 공개키 노출 자체가 현실적인 공격 벡터로 작동하지 않음이 확인되었다.
공개키 노출이라는 잠재적·이론적 위험보다, 실제로 반복 관측된 서명 가변성, 스크립트 및 멀티시그 프라이버시 누수, 합의 복잡성(다중서명, 스크립트 공개, 파싱의 복잡성 등) 문제를 더 시급한 위협으로 평가했다.
최악의 가정을 지연시키는 방어보다는, 당장 시스템의 안정성과 프라이버시를 저해하는 명확한 공격 표면을 제거하는 데 초점을 맞춘 설계적 전환의 결과다.
k (임시개인키)의 취약함
ECDSA는 k값을 임의로 정할 수 있었지만, 슈노르에서는 해시를 필수로 사용해야 한다.
256비트의 난수를 통해 k를 추출하여 낮은 k가 발생할 확률이 희박하다.
항목
ECDSA (RFC6979)
Schnorr (BIP340)
k 생성
결정론적 (HMAC)
결정론적 (tagged hash)
aux 난수
없음
있음 (권장)
parity 보정
없음
있음 (짝수만 사용)
공개키 입력
없음
포함 (P_x)
구조적 안전성
보통
더 강함
비선형구조의 한계
슈노르 서명의 가장 큰 특징은 여러개의 서명을 효율적으로 검증할 수 있다는 점이다.
ECDSA는 모든 서명을 각각 검증하여야 한다.
슈노르 서명은 서명값을 모두 더하는 방식으로 한 번에 검증할 수 있다.

슈노르 서명 메커니즘

기호
곡선: secp256k1
생성자: G
곡선 차수: n
개인키: d ∈ [1, n−1]
공개키: P = d·G (짝수만, 홀수인 경우 d=n-d)
메시지: m (보통 32바이트 sighash)
서명 준비
nonce k 생성 (hash 사용)
R = kG (짝수만, 홀수인 경우 k=n-k)
t=d⊕taggedHash(”BIP0340/aux”, aux)
k=taggedHash(”BIP0340/nonce”, t || P_x || m)
taggedHash란?
H_tag(m) = SHA256( SHA256(tag) || SHA256(tag) || m )
해시의 용도를 명확히 분리(domain separation)하기 위해 정의된 해시 방식
challenge e 생성 (메시지를 해시로 변형)
e=taggedHash(”BIP0340/challenge” || R_x || P_x || m)
서명 구하기
s=k+ed s=k+ed
서명 검증
sG=R+ePsG = R+eP
ECDSA 와 비교
구분
ECDSA
Schnorr
서명 공식
s = k⁻¹(z + r·d)
s = k + e·d
역원
필요
없음
선형성
없음
있음
집계 가능
불가
가능
서명 길이
가변
고정 64B
다중서명(MuSig) 절차
1.
공개키 집계 (계수 계산 후 키를 집계함)
ai=H(H(P1P2..Pn)Pi)Pagg=aiPia_i =H(H(P_1 || P_2||..P_n)||P_i) \\P_{agg}=\sum_{}^{}a_iP_i
2.
각자 nonce k 생성 후
MuSig (BIP-340 미적용)
각자 랜덤 k 생성 후 R을 구한다.
R을 taggedHash하여 각 commit 을 구한다.
commit 을 먼저 전송한 뒤에 R을 구한다. (나는 이 k를 쓸거고, 나중에 바꾸지 않겠다.)
3번의 교환 (commit, R, s)이 필요하다.
MuSig2 (BIP-340 적용)
ki=taggenHash(di,Pagg,Pi,session)k_i = taggenHash(d_i, P_{agg},P_i,session)
k의 방법을 미리 결정한다.
commit을 교환할 필요가 없어서 2회 교환으로 충분하다.
3.
R 추출 후 집계
R=RiR=\sum_{}^{}R_i
4.
공통 challenge 계산
e=H(RxPaggxm)e=H(R_x || P_{agg_x} || m)
5.
challenge를 통해 각자 서명 계산
si=ki+edis_i=k_i+e\cdot d_i
6.
서명의 합 계산
S=SiS=\sum_{}^{}S_i
7.
서명 제출