Scafonix (Serverless MPC) 연동기

Natreeum's Blog·2026년 7월 26일

Scafonix(Serverless MPC)로 지갑 구현하기

제가 지금껏 사용해온 Web2 서비스에서의 이탈 지점은 보통 "필요한 서비스가 아니어서", "재미가 없어서" 같은 이유였습니다. "로그인이 어려워서" 서비스를 그만둔 적은 거의 없었던 것 같습니다.

하지만 Web3에서는 이게 충분히 가능한 이탈 사유가 됩니다. 메타마스크 설치, 네트워크 추가, 시드 문구 백업, 가스비 준비까지 요구하면 사용자는 서비스에 들어오기도 전에 지칩니다. 그렇다고 서버가 개인키를 대신 보관하는 Custodial 방식은 보안적 책임이 너무 큽니다.

이번에는 Scafonix MPC 서비스를 사용해 Google 소셜 로그인만으로 테스트넷 지갑을 만들고, 직접 배포한 SimpleCounter 컨트랙트를 호출해봤습니다. Scafonix는 개인키를 P1, P2, P3로 나누는 2-of-3 MPC 방식입니다. 완성된 개인키를 DB에 저장하거나 서버에서 복구한 뒤 서명하는 구조가 아니라, 브라우저의 보안 샌드박스 안에서 필요한 share를 조합해 서명을 수행합니다. 서비스 제공자 입장에서는 별도의 키 보관 DB나 서명 서버를 운영하지 않아도 된다는 점이 가장 크게 와닿았습니다.

Scafonix Docs를 참고하여 이번 데모를 제작해 봤습니다. Scafonix Docs

Scafonix 에서는 AI Agent 가 활용할 수 있는 프롬프트도 지원하기 때문에 어렵지 않게 구현할 수 있습니다. AI Assistant Docs

1. SimpleCounter

테스트할 컨트랙트는 일부러 단순하게 만들었습니다. 사용자별 count만 저장하고, getCount()msg.sender의 count를 반환합니다. count()는 같은 msg.sender의 count를 1 증가시킵니다.

2. Google 로그인으로 지갑 연결

아래는 Scafonix 연동데모를 위해 만든 간단한 페이지 입니다.

클라이언트는 프레임워크 없이 HTML과 JS만 사용했습니다.
단 두줄(ethers 연동을 위한 코드라인을 빼면 단 한줄)만으로도 손쉽게 라이브러리를 불러와서 사용할 수 있습니다.

<script src="https://cdnjs.cloudflare.com/ajax/libs/ethers/6.13.1/ethers.umd.min.js"></script>
<script src="https://sdk.scafonix.com/sdk.js?v=2"></script>

Google Login 버튼을 누르면 Google OAuth 팝업이 나와서 구글계정으로 손쉽게 로그인할 수 있습니다.

로그인이 완료되면 별다른 과정 없이 나의 지갑이 생성되었고, 생성된 지갑주소를 확인할 수 있습니다.

Google Login만으로 Scafonix Wallet 연결까지 이어지는 간편한 UX가 구현됐습니다.

3. getCount와 count 호출

getCount()는 view 함수라서 단순히 RPC call로 조회했습니다. 다만 컨트랙트가 msg.sender 기준으로 count를 저장하기 때문에, call을 보낼 때도 Scafonix 지갑 주소를 from으로 넣어 현재 로그인한 사용자의 값을 읽었습니다.

const value = await contract.getCount({ from: connectedEvmAddress });

반면 count()는 실제 상태를 바꾸는 함수라 트랜잭션 서명이 필요합니다. Scafonix를 통해 tx에 서명하고 broadcast되는 과정은 아래 4단계로 이해하면 쉽습니다.

  1. 클라이언트에서 unsigned tx를 만든다
    먼저 to, data, chainId, nonce, gasLimit, fee 값을 채워 아직 서명되지 않은 transaction object를 만듭니다. SimpleCounter.count()의 경우 contract.count.populateTransaction()으로 호출 data를 만들고, RPC에서 nonce와 gas 정보를 조회합니다.

  2. Scafonix SDK에 서명을 요청한다
    만든 tx object를 Scafonix.signTransaction(connectedEvmAddress, tx)로 넘깁니다. 여기서 넘기는 것은 개인키가 아니라 서명할 트랜잭션 데이터입니다. device에 저장된 키를 Scafonix 서버로 전송해서 복구하는 구조가 아닙니다.

  3. SDK 보안 샌드박스에서 MPC 서명이 생성된다
    Scafonix는 P1, P2, P3 중 필요한 share 조합으로 2-of-3 threshold signing을 수행합니다. 문서 기준으로 이 과정에서도 완성된 private key는 메모리 안에서 조립되지 않습니다. EVM의 경우 결과값은 { r, s, v } 형태의 ECDSA signature입니다.

  4. 서명을 붙여 raw transaction을 만들고 RPC에 broadcast한다
    반환받은 signature를 ethers의 Transaction.from({ ...tx, signature }).serialized로 붙이면 signed raw transaction이 됩니다. 이 raw transaction을 provider.broadcastTransaction(signedTx)로 네트워크에 전송합니다.

const signature = await Scafonix.signTransaction(connectedEvmAddress, tx);
const signedTx = ethers.Transaction.from({ ...tx, signature }).serialized;
await provider.broadcastTransaction(signedTx);

서비스 제공자가 유저의 개인키를 들고 있지 않은데도, 사용자는 소셜 로그인 이후 자연스럽게 온체인 트랜잭션을 만들 수 있습니다.

4. 백업과 복구

P3는 사용자가 직접 보관하는 복구 share입니다. Google Drive나 copy key share 같은 옵션을 선택할 수 있습니다.
백업 모달은 showBackupModal(userId)로 열 수 있습니다.

Privy와 비교하며 느낀 차이

Privy 같은 Embedded Wallet도 Web3 온보딩 문제를 잘 풀어주는 선택지입니다. 다만 Scafonix는 서비스 제공자 서버 및 DB에 키를 저장하지 않으며, 서명 서버를 두지 않는다는 점이 강점입니다. P1은 브라우저, P2는 소셜 인증 기반 share, P3는 유저가 보관하는 백업 share로 나뉘고, 2-of-3 구조에서 서명이 일어납니다.

물론 "프론트엔드가 해킹돼도 무조건 안전하다"는 뜻은 아닙니다. 악성 트랜잭션 승인, 피싱, 스크립트 공급망 공격은 여전히 별도로 방어해야 합니다. 다만 개인키 전체를 앱이나 서버 및 DB에 저장하지 않는다는 점은 확실히 운영 부담을 줄여줄 수 있다고 생각합니다.

마무리

이번 데모에서는 Scafonix를 연동하여 Google 로그인 한 번으로 지갑 생성부터 연결까지 하고, 직접 배포한 SimpleCountergetCount()count()까지 호출했습니다. 메타마스크 설치 없이도 온체인 액션을 만들 수 있었고, 서비스 제공자 입장에서는 키를 따로 보관하는 설계를 하지 않아도 되기 때문에 정책적 리스크를 줄일 수 있으며 개별 키 보관 위험도 줄어든다는 점이 좋았습니다. 또한 분리된 키를 따로따로 보관하기 때문에 단일 share 유출만으로는 서명이 불가능하다는 점도 기존 private key 단일 보관 방식보다 안전하게 느껴졌습니다.

profile
BlockChain DEV

0개의 댓글