본문으로 건너뛰기
전체 글

결재 버튼 여러 개와 MPC 서명은 무엇이 다를까

· 약 14분
Bankware Global Engineering

담당자가 송금을 요청하고, 팀장과 책임자가 차례로 결재한다. 마지막에 서버가 보관한 개인키 하나로 거래에 서명한다면, 여러 사람이 업무를 승인했어도 암호학적으로 서명을 만드는 권한은 여전히 그 키를 사용하는 지점에 모여 있다.

MPC 지갑은 이 서명 권한을 여러 참여자에게 나누는 방식이다. 여기서 궁금해지는 것은 결재 화면의 개수가 아니라 계산 방법이다. 완전한 개인키를 한곳에 조립하지 않고, 세 명 중 두 명이 어떻게 하나의 유효한 서명을 만들까?

토큰 10개를 보내는 2-of-3 지갑을 예로 비밀 분산과 분산 키 생성부터 ECDSA 공동 서명까지 따라가 보자. 수학은 작은 수로 원리를 설명하고, 구현은 BXB의 사용자 참여형 MPC 경로와 연결한다.

같은 송금, 서로 다른 두 개의 문턱

한 EVM 네트워크에서 주소 A가 토큰 T 100개를 갖고 있고, 주소 B의 잔액은 0개라고 하자. 이번 거래는 A에서 B로 T 10개를 보내는 것이다. 성공하면 A에는 90개, B에는 10개가 남는다. T의 소수 자릿수는 0이고 발행·소각·토큰 전송 수수료·동시 거래는 없다고 가정한다. 거래 수수료에 필요한 기본 코인은 A에 따로 준비돼 있다.

A의 서명 권한은 참여자 P1, P2, P3에게 분산한다. 세 명 중 임의의 두 명이 참여해야 서명을 만들 수 있다. 이 조건을 2-of-3이라고 부른다. 주소 A와 참여자 P1은 서로 다른 개념이다. A는 체인이 보는 하나의 계정이고, P1은 그 계정의 서명 계산에 참여하는 주체다.

업무 시스템에는 여전히 “이번 송금을 해도 되는가”라는 결재 절차가 필요하다. MPC에서는 여기에 “서명을 만들 만큼의 참여자가 실제 계산에 참여했는가”라는 문턱이 더해진다. 업무 결재 두 건이 데이터베이스에 기록됐다는 사실만으로 MPC 서명이 생기지는 않는다.

구성서명 권한의 위치체인이 확인하는 것
복수 결재 후 단일 키 서명마지막 서명키를 사용하는 지점하나의 서명
컨트랙트 멀티시그여러 독립 키와 컨트랙트의 조건여러 서명과 실행 조건
임계값 ECDSA여러 참여자의 비밀 조각과 공동 계산하나의 ECDSA 서명

이 비교에서 멀티시그는 여러 소유자의 서명을 컨트랙트가 확인하는 일반적인 형태를 뜻한다. 임계값 ECDSA에서는 체인이 참여자별 서명을 세어 주지 않는다. 공동 계산의 결과가 일반 ECDSA 서명과 같은 방식으로 검증된다는 것이 핵심이다.

MPC는 Multi-Party Computation, 여러 주체가 각자의 비밀 입력을 공개하지 않고 함께 계산하는 기술의 범주다. 그중 서명을 공동 계산하는 적용을 임계값 서명, TSS(Threshold Signature Scheme)라고 한다. 따라서 MPC라는 이름만으로 곡선, 참여 인원, 통신 횟수나 보안 조건까지 결정되지는 않는다. NIST의 임계 암호 설명도 키와 연산의 분산을 함께 다룬다.

키 조각은 비밀번호를 세 토막 낸 것이 아니다

먼저 “두 조각이면 충분하고 한 조각이면 부족하다”는 조건을 작은 수로 만들어 보자. 계산을 쉽게 하려고 모든 수를 17로 나눈 나머지로 다룬다. 이를 mod17\bmod 17이라고 쓴다. 실제 지갑의 큰 수나 보안 강도를 재현하는 예제가 아니다.

비밀값을 x=5x=5로 두고, 무작위로 고른 기울기 3을 사용해 일차식을 만든다.

f(u)=5+3u(mod17)f(u)=5+3u \pmod{17}

비밀은 u=0u=0일 때의 값이다. 각 참여자에게는 서로 다른 점의 값 하나씩을 준다.

참여자입력 uu받은 값 f(u)f(u)
P118
P2211
P3314

P1과 P2의 값으로 비밀을 구하면 다음과 같다.

x=2×811=5(mod17)x=2\times 8-11=5\pmod{17}

두 점으로 직선을 정하고, 그 직선의 u=0u=0인 지점을 찾은 것이다. P1과 P3, P2와 P3를 골라도 같은 비밀값을 구할 수 있다. 반면 P1이 아는 (1,8)(1,8) 한 점만으로는 기울기와 절편을 정할 수 없다. 기울기가 적절히 무작위로 선택됐다면 비밀 후보마다 그 점을 지나는 직선이 존재한다.

이것이 Shamir 비밀 분산의 출발점이다. mm명이 있어야 비밀을 구하도록 하려면 차수가 m1m-1인 다항식을 사용한다. 2-of-3은 차수 1, 3-of-5는 차수 2다. 여기서 mm은 필요한 최소 인원이며 전체 인원 nn과 구별한다.

이 예제는 조각들 사이의 수학적 관계를 보여 준다. MPC 서명 때 위 계산으로 개인키를 복원해 서버에 올린다는 뜻은 아니다. 충분한 조각을 모으면 비밀을 복원할 수 있지만, 임계값 서명 프로토콜은 조각을 각자 가진 채 서명에 필요한 연산을 수행하도록 설계된다. “복원 가능하다”와 “서명할 때 복원한다”는 다른 이야기다.

처음부터 완전한 키를 만들지 않는 방법

앞의 예제에서는 누군가가 비밀 5를 알고 다항식을 나눠 줬다. 그 주체가 실제 지갑의 개인키를 처음부터 알고 있다면, 생성 단계에 권한이 집중될 수 있다. 분산 키 생성, DKG(Distributed Key Generation)는 참여자들이 함께 키를 만들도록 이 단계를 바꾼다.

2-of-3의 원리를 단순화하면 각 참여자 PjP_j가 자기만 아는 두 값 aj,bja_j,b_j를 무작위로 골라 개별 다항식을 만든다. 아래에서 qq는 비밀값 계산에 사용하는 큰 소수다. 실제 ECDSA에서는 곡선의 기준점을 몇 번 더하면 항등원으로 돌아오는지를 나타내는 수, 기준점의 위수에 해당한다.

fj(u)=aj+bju(modq)f_j(u)=a_j+b_j u\pmod q

P1은 자신의 다항식을 계산해 P2에게 f1(2)f_1(2)를, P3에게 f1(3)f_1(3)을 비밀리에 전달한다. P2와 P3도 같은 작업을 한다. 각 참여자 PiP_i는 자기가 계산한 fi(i)f_i(i)와 다른 참여자에게 받은 fj(i)f_j(i)를 합쳐 최종 조각 σi\sigma_i를 얻는다.

σi=j=13fj(i)(modq)x=j=13aj(modq)\begin{aligned} \sigma_i&=\sum_{j=1}^{3}f_j(i)\pmod q\\ x&=\sum_{j=1}^{3}a_j\pmod q \end{aligned}

두 번째 줄의 xx는 이렇게 생성된 공동 개인키를 수학적으로 표현한 것이다. 어떤 서버가 a1,a2,a3a_1,a_2,a_3을 받아 합치는 단계는 필요하지 않다. 각자가 최종적으로 갖는 것은 자기 조각 σi\sigma_i다.

공개키는 공개 가능한 값들을 더해서 만들 수 있다. 타원곡선의 기준점을 GG라고 하면 각 참여자는 ajGa_jG를 공개하고, 이 점들을 합쳐 공동 공개키 QQ를 얻는다.

Q=j=13ajG=xGQ=\sum_{j=1}^{3}a_jG=xG

ajGa_jG는 타원곡선 위의 점이며 aja_j 자체가 아니다. 적절한 곡선과 매개변수에서는 이 점에서 aja_j를 알아내기 어렵다는 이산로그 가정에 기대고 있다. 공개키를 합칠 수 있다는 사실이 개인키도 공개된다는 뜻은 아니다.

여기에는 한 가지 문제가 남는다. P1이 P2와 P3에게 서로 맞지 않는 값을 보내면 어떻게 할까? 검증 가능한 비밀 분산, VSS(Verifiable Secret Sharing)는 받은 값이 약속된 다항식과 맞는지 확인할 수 있게 한다. 간단한 계수 커밋먼트 모델에서는 P1이 C1,0=a1GC_{1,0}=a_1G, C1,1=b1GC_{1,1}=b_1G를 공개하고, P2는 받은 값으로 다음 관계를 검사한다.

f1(2)G=C1,0+2C1,1f_1(2)G=C_{1,0}+2C_{1,1}

커밋먼트는 비밀값을 직접 공개하지 않으면서, 그 값에 관한 나중의 설명을 확인하는 데 쓰는 자료다. 이 검사는 P1이 보낸 값과 공개한 계수 사이의 일관성을 확인한다. 실제 DKG에는 인증된 통신, 증명, 잘못된 참여자에 대한 처리 등 추가 절차가 필요하다. 위 식은 그중 비밀 조각의 일관성을 검증하는 원리를 설명한 것이며 완전한 DKG 구현 명세는 아니다. Feldman의 VSS 논문은 이런 검증의 기초를, 분산 키 생성 연구는 악의적 참여자까지 고려할 때 필요한 조건을 다룬다.

ECDSA 서명에서 공동 계산해야 하는 것

키를 분산한 다음에는 거래에 서명해야 한다. ECDSA는 타원곡선을 이용하는 전자서명 방식이다. 공동 개인키가 xx, 공개키가 Q=xGQ=xG일 때 서명 대상 메시지의 해시에서 얻은 정수를 zz라고 하자. 서명 한 번에 사용할 비밀값 kk1k<q1\le k<q 범위에서 정하면 기본 서명식은 다음과 같다.

R=kGr=xcoord(R)modqs=k1(z+rx)modq\begin{aligned} R&=kG\\ r&=\operatorname{xcoord}(R)\bmod q\\ s&=k^{-1}(z+rx)\bmod q \end{aligned}

xcoord\operatorname{xcoord}는 곡선 위 점의 x좌표를 뜻한다. k1k^{-1}kk와 곱하면 나머지가 1이 되는 모듈러 역원이다. 서명 결과는 두 정수 (r,s)(r,s)이며, 1r,s<q1\le r,s<q 범위를 만족해야 한다. 여기서 kk는 서명마다 필요한 비밀 nonce다. 계정의 거래 순번을 나타내는 공개된 트랜잭션 nonce와는 다르다.

검증자는 개인키나 kk를 몰라도 된다. w=s1modqw=s^{-1}\bmod q를 구하고 다음 점을 계산한다.

V=(zw)G+(rw)QV=(zw)G+(rw)Q

서명이 올바르면 V=kGV=kG가 되므로, 이 점의 x좌표를 qq로 나눈 값이 rr과 일치한다. 실제 검증에서는 서명 범위, 공개키와 점의 유효성도 검사한다. 이 서명·검증 관계는 ECDSA를 정의한 FIPS 186-5에서 확인할 수 있다.

임계값 ECDSA도 마지막에는 이 검증을 통과하는 (r,s)(r,s)를 만들어야 한다. 차이는 서명식의 비밀 입력을 한 사람이 모두 갖고 있지 않다는 데 있다. 참여자들은 분산된 xx를 사용하고, 서명용 비밀값과 중간 계산도 프로토콜에 따라 나누어 다룬다.

조각마다 서명해서 더하면 되지 않을까

선택된 두 참여자의 조각에는 각자 가중치를 곱할 수 있다. 앞의 작은 예제에서 P1의 가중치는 2, P2의 가중치는 1-1이었다. 이런 보간 가중치를 λi\lambda_i라고 하면, 서명에 참여하는 집합 SS에 대해 다음 관계가 성립한다.

x=iSλiσi(modq)x=\sum_{i\in S}\lambda_i\sigma_i\pmod q

각 참여자가 가중치를 적용한 자기 값으로 계산하면 되므로, 합에 해당하는 연산은 비교적 자연스럽게 나눌 수 있다. 그러나 ECDSA의 s=k1(z+rx)s=k^{-1}(z+rx)에는 비밀값의 역원과 곱셈이 들어 있다. 참여자마다 임의의 nonce로 완성한 ECDSA 서명을 만든 뒤 더한다고 같은 공개키의 서명이 되지는 않는다. 공동의 rr을 만들고, kkxx를 노출하지 않은 채 곱과 역원에 필요한 중간값을 계산해야 한다.

곱셈의 어려움은 비밀을 두 개의 덧셈 조각으로 표현해 보면 드러난다.

(a1+a2)(b1+b2)=a1b1+a2b2+a1b2+a2b1\begin{aligned} (a_1+a_2)(b_1+b_2) &=a_1b_1+a_2b_2\\ &\quad+a_1b_2+a_2b_1 \end{aligned}

각자가 자기 조각의 곱 a1b1a_1b_1, a2b2a_2b_2만 계산하면 상대의 조각과 곱해야 하는 교차항이 빠진다.

이때 쓰이는 기법 중 하나가 MtA(Multiplicative-to-Additive)다. 한 참여자의 비밀 aa와 다른 참여자의 비밀 bb에 대해, 두 사람이 나눠 가진 결과 α,β\alpha,\beta가 다음 관계를 만족하도록 계산한다.

α+β=ab(modq)\alpha+\beta=ab\pmod q

한쪽이 상대의 비밀을 받아 직접 곱하는 대신, 곱셈 결과를 다시 분산된 덧셈 조각으로 얻는 것이다. Paillier처럼 암호문 상태에서 덧셈과 알려진 상수의 곱셈을 지원하는 암호가 이 과정에 사용될 수 있다. 참여자들은 암호화된 값과 무작위 마스크를 이용해 계산하고, 각자에게는 결과의 일부만 남긴다.

이 짧은 식만 구현한다고 안전한 MtA가 완성되는 것은 아니다. Paillier와 타원곡선의 계산 범위는 서로 다르며, 악의적인 참여자가 범위를 벗어난 값이나 잘못된 암호문을 넣는 경우도 다뤄야 한다. 그래서 실제 프로토콜에는 범위 증명과 영지식 증명 같은 추가 장치가 들어간다. 영지식 증명은 비밀을 공개하지 않고도 정해진 관계를 만족한다는 사실을 확인하는 방법이다.

역원도 마스킹한 곱을 이용해 생각할 수 있다. 아무도 전체 값을 알지 못하도록 분산된 무작위 비밀 γ\gamma를 준비하고, 공동 계산으로 δ=kγmodq\delta=k\gamma\bmod q를 얻는다고 하자. kkγ\gamma는 0이 아니어야 한다. 적절히 생성한 무작위 마스크 아래에서는 δ\delta만 공개하고도 다음 관계를 사용할 수 있다.

k1=δ1γ(modq)k^{-1}=\delta^{-1}\gamma\pmod q

공개된 δ\delta의 역원은 각자가 계산할 수 있다. 따라서 γ\gamma의 덧셈 조각마다 공개값 δ1\delta^{-1}를 곱하면 k1k^{-1}의 덧셈 조각을 얻는다. 이는 비밀 역원 계산을 이해하기 위한 골격이며, 마스크 생성과 곱셈·공개 단계의 증명은 안전한 프로토콜이 제공해야 한다. Gennaro의 임계값 ECDSA 설명에서 이 대수적 구성을, 관련 논문에서 공동 계산의 구성을 더 자세히 볼 수 있다.

임계값 ECDSA의 통신 라운드는 이런 중간값, 커밋먼트와 증명을 주고받는 과정이다. 최종 서명 조각을 결합하는 단계가 있더라도 그 전에 공동 계산이 필요하다. 라운드 수와 사전 계산 방식은 프로토콜마다 다르므로, MPC라는 이름으로 고정된 횟수를 말할 수는 없다.

서명용 nonce는 왜 한 번만 써야 할까

서명용 비밀 kk를 잘못 재사용하면 키 분산의 효과도 무너질 수 있다. 같은 개인키와 같은 kk로 서로 다른 해시 z1,z2z_1,z_2를 서명했다고 하자. 두 서명은 같은 rr을 가지며, 다음 관계를 만족한다.

s1s2=k1(z1z2)(modq)s_1-s_2=k^{-1}(z_1-z_2)\pmod q

두 해시의 차이가 qq의 배수가 아니어서 역원이 존재하면, 공개된 두 서명에서 다음 값을 계산할 수 있다.

k=(z1z2)(s1s2)1modqx=(s1kz1)r1modq\begin{aligned} k&=(z_1-z_2)(s_1-s_2)^{-1}\bmod q\\ x&=(s_1k-z_1)r^{-1}\bmod q \end{aligned}

결국 완전한 개인키 xx가 드러난다. 그래서 안전한 nonce 생성과 일회성 자료 관리는 키 조각 보관만큼 중요하다. 서명이 중단됐다고 이전 세션의 중간값을 임의로 다음 메시지에 재사용해도 되는 것은 아니다. 사전 계산 자료마다 용도가 다르므로, 어떤 자료를 재사용할 수 있는지는 해당 프로토콜의 조건을 따라야 한다.

여기까지의 수식은 임계값 서명이 지켜야 할 계산 관계와 보안 조건을 설명한다. 직접 암호 프로토콜을 구현하기 위한 절차서로 읽기보다는, 구현이 왜 조각 저장 외에도 많은 연산과 통신을 요구하는지 이해하는 기준으로 삼을 수 있다.

BXB에서는 이 계산이 어디에서 실행될까

BXB의 사용자 참여형 경로에서는 브라우저 확장 프로그램이 각 참여자의 MPC 연산을 이어 간다. 암호 계산 모듈은 Go 코드를 WebAssembly로 실행하고, tss-lib v3.0.0의 secp256k1 키 생성·서명 API를 사용한다. secp256k1은 이 EVM 계정 서명에 사용하는 타원곡선이다.

2-of-3의 최소 참여 인원은 2지만, 라이브러리에 넘기는 threshold는 1이다. 이 API가 threshold + 1명을 요구하기 때문이다. 업무 설정의 “필요 인원”과 암호 라이브러리의 매개변수가 같은 숫자라고 가정하면 안 된다.

키 생성 결과도 단순한 정수 하나는 아니다. 구현은 참여자별 비밀 조각과 프로토콜에 필요한 보조 자료를 저장하고, 공동 공개키와 주소를 별도로 만든다. 암호 모듈에는 Paillier와 관련된 사전 계산 자료를 구성하고 검증하는 경로도 있다. 서명할 때는 자신의 저장 자료와 이번에 선택된 참여자 목록으로 새 서명 세션을 시작한다. 앞의 σi\sigma_i는 이 저장 자료 중 비밀 분산 관계를 설명하기 위한 수학적 표현이다.

그다음 확장 프로그램이 계산 모듈에서 나오는 메시지를 상대 참여자에게 전달하고, 받은 메시지를 다시 모듈에 넣는다. 상대의 완전한 키 조각을 받아 한곳에서 서명하는 흐름이 아니다. 중계와 세션 조정은 이 공동 계산의 통신을 이어 주는 역할을 한다. 통신을 전달하는 권한, 지갑의 서명 권한, 업무 결재 권한은 각각 구분해 설계해야 한다.

이 설명은 소스에서 확인한 사용자 참여형 키 생성·서명 흐름을 기준으로 한다. 앞서 살펴본 DKG와 MtA 수식은 원리 설명이며, 제품 내부 프로토콜의 모든 라운드를 그대로 옮긴 것은 아니다.

공동 서명에서 하나의 EVM 거래까지

처음의 T 10개 전송으로 돌아가 보자. P1과 P2가 이번 서명에 참여한다고 하자. 처리 흐름을 나누면 다음과 같다.

  1. 업무 요청을 정한다. 누구의 어떤 자산을 어디로 보낼지와 허용 여부를 업무 시스템에서 다룬다.
  2. 서명할 거래를 준비한다. 네트워크, 계정 nonce, 수신 대상, 호출 데이터와 수수료 조건을 포함한 미서명 거래를 만든다.
  3. 공동 계산의 입력을 맞춘다. 선택된 참여자들이 준비된 거래의 같은 서명 대상 해시를 입력으로 사용한다.
  4. MPC 라운드를 수행한다. 각자가 자기 저장 자료로 계산하고 프로토콜 메시지를 주고받아 최종 r,sr,s를 얻는다.
  5. EVM 거래를 구성하고 제출한다. 서명으로부터 기대한 주소가 복구되는지 확인하고, 서명이 포함된 거래를 직렬화해 제출한다.
  6. 체인의 결과를 별도로 확인해야 한다. 제출 응답만으로 끝내지 않고 실행 영수증과 필요한 확정 수준을 확인한다.

ERC-20 전송에서는 바깥 거래의 수신 대상이 토큰 컨트랙트이고, 실제 토큰 수취인 B와 수량 10은 컨트랙트 호출 데이터에 들어간다. 따라서 사람이 확인하는 “B에게 10개”와 서명 대상 바이트 사이의 연결도 중요하다. 같은 해시에 서명했다는 사실만으로 참여자가 그 거래의 업무 의미를 올바르게 이해했다는 것까지 증명되지는 않는다.

BXB의 암호 모듈은 입력받은 해시로 서명 계산을 하고 r,sr,s를 반환한다. 확장 프로그램은 이 값과 복구용 parity 후보를 사용해 주소를 복구하고, 기대한 지갑 주소와 일치하는 결과로 서명된 EVM 거래를 구성한다. 체인에 올라가는 것은 P1의 서명과 P2의 서명 두 개가 아니라 하나의 계정 서명이다.

이때 서명 대상 해시와 제출된 거래의 해시는 서로 다르다. 전자는 서명할 내용을 특정하고, 후자는 서명까지 포함한 거래를 식별한다. 어느 쪽 해시를 얻었든 그것만으로 토큰 전송이 성공했다고 판단할 수는 없다. 실행과 확정이 확인돼야 예제의 잔액을 A 90개, B 10개로 마무리할 수 있다. 거래가 소비한 기본 코인 수수료는 A가 별도로 부담한다. MPC는 서명 권한을 나누는 기술이며 가스비 대납 여부는 별도의 설계다.

고객 계정에서 사용할 지갑을 선택하고 키 사용을 허용하는 과정은 BXB의 고객 계정과 서명 연결에서 설명했다. MPC를 적용하면 그 연결의 서명 단계에 여러 참여자의 공동 계산이 들어간다.

참여자 한 명이 사라지면 무엇이 달라질까

P3가 접속하지 못하더라도 P1과 P2가 참여할 수 있으면, 2-of-3 조건을 만족하는 새 서명 세션을 구성할 수 있다. 반대로 P1과 P2를 선택한 세션에서 P2가 중단되면 P1 혼자 나머지 서명을 완성할 수는 없다. P3가 남아 있다는 이유로 진행 중인 계산에 그대로 끼워 넣을 수 있다고 가정해서도 안 된다. 참여자 집합과 세션 자료를 프로토콜에 맞게 다시 다뤄야 한다.

서명 생성 전에 중단돼 거래를 제출하지 않았다면, 그 시도로 인한 온체인 수수료는 없고 잔액도 A 100개, B 0개다. 이미 제출한 뒤 응답을 놓쳤다면 상황이 다르다. 체인에서 실행됐을 가능성을 먼저 확인해야 한다. 업무 요청의 완료, 공동 서명의 완료, 체인의 실행 완료는 서로 다른 상태다.

또한 2-of-3은 두 참여자가 공모해도 거래를 막아 준다는 뜻이 아니다. 두 명이 필요한 비밀 자료를 가지고 협력하면 서명할 수 있는 것이 그 설정의 의미다. 한 운영자가 세 참여자의 장치와 백업을 모두 통제한다면 기대한 권한 분리가 약해진다. 참여자 인증, 독립적인 보관, 업무 정책과 감사 기록을 함께 설계해야 한다.

MPC로 달라지는 것은 유효한 서명을 만들기 위해 필요한 협력의 구조다. 우리 예제에서 P1과 P2는 완전한 개인키를 한곳에 모으지 않고 하나의 ECDSA 서명을 만든다. 업무 시스템은 그 송금이 허용되는지 판단하고, 체인은 그 서명과 거래를 검증해 상태를 바꾼다. 이 세 역할을 연결해야 “두 사람이 결재했다”는 기록을 넘어, 실제 자산을 움직이는 권한도 나눌 수 있다.