sayu.day
Blockchain

Non-Custodial 지갑 UX의 본질은 키 위치가 아니라 권한 경계다

좋은 지갑 UX를 만들기 위해 서버와 인프라가 많은 일을 하더라도, 사용자 승인 권한까지 가져가면 안 됩니다. account abstraction, passkey, MPC, zkLogin을 살펴보며 non-custodial 경계를 정리합니다.

발행 2026년 5월 5일122,373

같은 주제에서 이어 읽기

go-ethereum Merkle Trie를 활용한 데이터 무결성 검증

Blockchain 안에서 이어지는 글

좋은 지갑 UX를 만들려면 사용자가 신경 쓰지 않아도 되는 일이 많아야 한다. 지갑 주소를 찾아주고, 트랜잭션을 조립하고, 수수료를 계산하고, 필요하면 가스도 대신 내준다. 사용자는 지갑이라는 도구를 쓰고 싶다기보다, 어떤 서비스 안에서 자연스럽게 자산을 이동하거나 권한을 행사하고 싶어 한다.

그런데 이 편의성을 따라가다 보면 금방 애매한 지점에 도착한다. 서버가 트랜잭션 전송을 도와주는 것은 괜찮은가? 서버가 사용자의 지갑 주소를 알고 있는 것은 괜찮은가? 서버가 자동결제 시점에 트랜잭션을 대신 전송하는 것은 어디까지 허용할 수 있을까?

이 질문들을 조사하면서 non-custodial의 기준은 생각보다 단순하지 않다는 것을 느꼈다. 개인키를 서버 DB에 저장하지 않는다고 해서 항상 충분한 것은 아니다. 더 정확히는 아래 기준을 최소 보안선으로 먼저 봐야 했다.

사용자 외의 주체가 사용자 개입 없이 유효한 wallet approval을 만들 수 없어야 한다.

다만 이것만으로 충분하지는 않다. recovery, owner change, module install, upgrade, session key 발급, delegation 변경처럼 우회 가능한 control path가 남아 있다면 여전히 custodial 또는 quasi-custodial risk가 남는다.

relayer, bundler, paymaster 같은 인프라는 사용자 경험을 좋게 만들기 위해 필요할 수 있다. 하지만 그 인프라가 단순 전송자나 후원자를 넘어 사용자 승인에 준하는 실행 권한을 재현할 수 있다면, 그때부터는 non-custodial이라고 말하기 어려워진다.

그래서 지갑 UX를 설계할 때 가장 먼저 나눠야 하는 것은 키의 위치보다 권한의 의미라고 생각하게 됐다.

Account Session ≠ Root Wallet Execution Authority ≠ Recovery Authority

계정 세션은 사용자를 식별하고 지갑 주소를 찾는 데 쓸 수 있다. 하지만 계정 세션만으로 자산 이동, owner 변경, 복구 정책 변경이 가능하면 안 된다. 지갑 실행 권한은 특정 action에 묶인 사용자 승인 서명이어야 하고, 복구 권한은 평상시 결제 권한과 분리되어야 한다.

자동결제, session key, permission module은 네 번째 root authority가 아니라 Root Wallet Execution Authority에서 파생된 제한 권한으로 보는 편이 정확하다. 이 권한은 target, selector, 금액, 기간, nonce, 취소 가능성 같은 조건으로 좁혀져야 한다.

다만 non-custodial은 trustless나 permissionless와 같은 말은 아니다. relayer, bundler, paymaster는 사용자 승인을 만들지 못하더라도 검열, 지연, 가스 후원 거부, 정책 게이팅, 메타데이터 노출 같은 운영상 통제력을 가질 수 있다. 그래서 "누가 서명할 수 있는가"와 함께 "누가 전송을 막을 수 있는가"도 별도의 축으로 봐야 한다.

PAKE는 문제가 아니라, 쓰는 위치가 문제다

처음에는 PAKE가 이런 구조에 맞는지 궁금했다. PAKE는 password를 직접 서버에 보내지 않고도 인증을 성립시키는 방식이다. 이 성질만 보면 오히려 좋은 인증 레이어처럼 보인다. 서버가 password를 모르고, 인증 과정에서도 password 자체가 노출되지 않기 때문이다.

문제는 PAKE 자체가 아니라 PAKE의 결과를 어디에 연결하느냐다. PAKE 출력이나 password-derived material이 지갑 서명 재료로 이어지면, 인증 복구와 지갑 복구가 같은 권한 경계 안으로 들어온다. password reset이나 계정 복구가 곧 owner 변경으로 확장될 수 있다면, 사용자는 더 이상 독립적으로 지갑 실행 권한을 통제한다고 보기 어렵다.

PAKE에도 여러 프로토콜이 있다. 이 글의 관심은 특정 PAKE 방식이 아니라, PAKE 결과를 wallet control에 연결하는 사용 모델이다.

그래서 PAKE를 사용한다면 역할을 명확히 제한해야 한다.

PAKE = account/session authentication
Passkey / WebAuthn assertion = wallet approval로 인정될 수 있는 사용자 기기 기반 증명 재료
Recovery proof = approval method 복구/교체
Policy / permission module = root execution에서 파생된 제한 실행 권한

PAKE 인증 성공은 지갑 주소 조회나 복구 flow 진입을 돕는 신호일 수 있다. 하지만 owner approval, recovery proof, 자동결제 실행 권한이 되어서는 안 된다. 계정 인증은 계정 인증으로 끝나야 하고, 지갑 실행 권한은 별도의 서명이나 Smart Account가 검증하는 정책이 담당해야 한다.

결국 포기해야 하는 것은 PAKE가 아니라, password나 PAKE reset만으로 wallet control까지 복구되는 모델이다. 이 모델은 UX는 편하지만, 인증 시스템이 지갑 권한까지 회복시킬 수 있다는 뜻이기도 하다.

Passkey-first 구조에서 분리해야 할 것

가장 자연스럽게 보인 방향은 passkey-first 구조였다. 사용자는 WebAuthn passkey ceremony를 통해 특정 action에 대한 authentication assertion, 즉 사용자 기기에서 나온 서명 재료를 만든다. 이것이 곧바로 wallet approval이 되는 것은 아니다. Wallet approval로 인정되려면 challenge가 지갑 실행 문맥에 강하게 binding되어야 하고, Smart Account verifier가 그 binding을 검증해야 한다.

이때 challenge는 단순히 userOpHash를 담는 필드가 아니다. WebAuthn 관점에서는 relying party가 신뢰할 수 있는 환경에서 만든 충분한 엔트로피의 single-use challenge여야 하고, 응답으로 돌아온 challenge와 정확히 일치해야 한다. 그 단회성 challenge 안팎에 wallet, chain, EntryPoint, action 또는 userOpHash, nonce, 만료 시간을 명시적으로 묶어야 replay와 context confusion을 줄일 수 있다.

백엔드나 중개 인프라는 action 생성, preview 구성, 수수료 계산, 전송 상태 관리를 도울 수 있다. 하지만 사용자를 대신해 approval을 만들면 안 된다.

여기서 중요한 점은 사용자가 승인하는 의미 단위와 실제 실행 포맷을 분리하는 것이다.

Action
→ ERC-4337 UserOperation

또는

Action
→ 다른 실행 포맷

여기서 Action은 ERC-4337 표준이 정의하는 native object가 아니라, 사용자가 무엇을 승인했는지 표현하기 위한 application layer 추상화다. ERC-4337에서 실제 검증 단위는 PackedUserOperationuserOpHash이고, Smart Account의 validateUserOp가 이를 검증한다. 따라서 Action-first 구조를 쓰려면 사용자가 최종 userOpHash 자체를 승인하거나, Action Hash와 컴파일된 UserOperation 필드가 같은 의미를 가진다는 것을 Smart Account가 엄격히 확인해야 한다.

사용자가 승인하는 의미 단위에는 최소한 지갑 주소, 체인 ID, 신뢰할 EntryPoint, factory/initCode 또는 배포 경로, 대상 contract, method, value, calldata hash, gas/payment 경계, sponsor 관련 필드의 허용 범위, preview hash, nonce, validity window가 포함되어야 한다. EIP-7702를 함께 사용한다면 delegation address나 eip7702Auth도 같은 binding 안에 들어와야 한다.

특히 previewHash가 중요하다. 사용자가 화면에서 본 문구, 금액, 대상, 수수료 정책과 실제 실행 payload가 달라지면 안 된다. 만약 payload가 바뀌면 Action Hash도 바뀌어야 하고, 기존 approval assertion은 실패해야 한다. 그렇지 않으면 사용자가 본 것과 다른 일을 실행하는 구조가 된다.

이 구조에서 백엔드는 많은 일을 할 수 있다. 하지만 백엔드가 판단하는 "승인됨" 상태는 최종 권한 근거가 아니다. Smart Account가 서명과 검증 규칙을 확인하고, nonce와 validity window로 replay를 막아야 한다. 결제, recovery, admin action처럼 성격이 다른 흐름은 nonce lane이나 validation rule을 분리하는 편이 안전하다.

ERC-4337에서 paymaster도 별도의 권한 경계를 가진다. account의 validateUserOp는 사용자 권한을 검증하고, paymaster의 validatePaymasterUserOp는 가스 후원 여부를 판단한다. Paymaster signature는 wallet signature가 아니다. Bundler도 UserOperation을 시뮬레이션하고 릴레이할 뿐, owner approval을 만들어서는 안 된다.

Passkey를 사용할 때도 한 가지 주의할 점이 있다. WebAuthn은 public key credential이 relying party에 scope되고, authenticator가 사용자 동의 없이 operation을 수행하지 않는 모델을 제공한다. W3C WebAuthn 검증 흐름에는 challenge, origin, RP ID hash, user presence, 필요한 경우 user verification, signature counter 같은 항목이 포함된다. 하지만 온체인 verifier가 이 의미를 모두 재검증한다고 가정하면 안 된다.

실제 blockchain용 WebAuthn verifier는 origin 검증, RP ID hash 검증, signature counter 검증을 의도적으로 생략하기도 한다. 따라서 "passkey를 쓴다"는 사실만으로 충분하지 않고, 어떤 필드를 어디서 검증하는지, high-assurance flow에서 user verification을 필수로 요구하는지, web-origin/phishing 방어가 필요하면 어떤 app/origin 식별자를 challenge에 묶을지를 별도로 threat model에 넣어야 한다.

또한 passkey에는 device-bound passkey와 synced passkey 같은 구현 차이가 있다. Synced passkey는 passkey provider나 cloud 계정을 통해 여러 기기로 복구·배포될 수 있으므로, 단일 디바이스만 신뢰한다는 가정이 깨진다. 지갑 UX에서는 이 차이가 곧 recovery trust assumption의 차이가 된다.

EVM에서 P-256/secp256r1 검증을 사용하는 경우에도 네트워크의 precompile 지원, verifier contract 비용, 라이브러리 선택에 따라 구현 제약이 달라진다. EIP-7951은 secp256r1 검증 precompile을 제안하며, Apple Secure Enclave, Android Keystore, FIDO2/WebAuthn authenticator 같은 현대 인증 하드웨어의 서명을 효율적으로 검증하려는 흐름과 맞닿아 있다. 하지만 이것이 모든 EVM 환경에서 이미 같은 비용과 같은 방식으로 가능하다는 뜻은 아니다.

고보증 지갑 UX를 설계한다면 "passkey를 쓴다"에서 멈추지 말고, 어떤 authenticator와 어떤 sync 모델, 어떤 온체인 verifier를 신뢰하는지도 별도로 봐야 한다.

EIP-7702에서도 사라지지 않는 root authority bypass

최근 account abstraction 흐름에서 EIP-7702도 빼놓기 어렵다. 2025년 5월 7일 메인넷 활성화가 공지된 Pectra 업그레이드에 포함된 EIP-7702는 EOA가 특정 contract code에 delegation하여 batching, sponsorship, 제한된 권한 실행 같은 smart account UX를 일부 얻을 수 있게 한다. EOA를 완전히 Smart Account로 바꾸지 않아도 기존 주소 위에 더 풍부한 실행 로직을 얹을 수 있다는 점에서 중요하다.

하지만 delegation이 곧 non-custodial safety를 보장하지는 않는다. 어떤 contract에 delegate하는지, 그 contract가 어떤 module과 upgrade path를 갖는지, 기존 EOA key가 정책을 우회할 수 있는지, relayer가 단일 장애점이 되는지까지 봐야 한다. Ethereum.org의 7702 가이드도 delegation 이후에도 EOA private key가 계정에 대한 full control을 유지한다고 설명한다. Safe 같은 contract에 delegate했다고 해서 곧바로 multisig가 되는 것은 아니고, 단일 EOA key가 signing policy를 우회할 수 있다.

EIP-7702에서는 delegation 자체의 수명주기와 초기화 리스크도 중요하다. Delegation은 persistent하며, 트랜잭션 실행이 revert되어도 처리된 delegation indicator는 롤백되지 않는다. chain_id = 0 authorization은 다중 체인 리스크를 키울 수 있고, initialization calldata를 적절히 서명 검증하지 않으면 frontrunning으로 잘못된 초기 owner나 module 구성이 주입될 수 있다. Delegation contract를 바꿔도 기존 storage가 지워지는 것은 아니므로 storage collision이나 migration 리스크도 검토해야 한다.

결국 EIP-7702에서도 핵심 질문은 동일하다.

누가 사용자 개입 없이 wallet control path를 만족시킬 수 있는가?

표면상 smart account UX가 생겼는지보다, 실제 control path와 bypass path가 무엇인지 보는 쪽이 더 중요하다.

자동결제는 서버 서명이 아니라 사전 위임 권한이다

자동결제는 가장 애매한 부분이었다. 매번 Passkey 승인을 요구하면 사용성이 떨어진다. 반대로 서버가 signed action을 저장해두고 나중에 재해석해서 실행하면, 서버가 실질적인 실행 권한을 갖는 구조가 된다.

그래서 자동결제는 "서버가 나중에 대신 서명하는 구조"가 아니라 "사용자가 미리 승인했고 Smart Account가 매번 검증하는 사전 위임 권한"으로 보는 것이 맞다고 느꼈다. 구현은 온체인 policy registry일 수도 있고, session key나 permission module일 수도 있다. 중요한 것은 반복 실행 권한의 범위가 Smart Account 검증 규칙으로 제한되어야 한다는 점이다.

예를 들어 자동결제 policy에는 이런 조건들이 들어갈 수 있다.

merchant
amountCap
interval
expiry
optional allowedExecutor
nonce / replay 방지

allowedExecutor는 항상 보안 필수조건이라기보다 가용성, 검열 저항, 운영 통제 사이의 trade-off다. 핵심 경계는 특정 relayer를 믿는 데 있지 않고, target, selector, 금액 한도, 주기, 만료, policy nonce 같은 validation constraint를 Smart Account가 직접 강제하는 데 있다.

사용자는 처음에 이 policy 등록 Action을 승인한다. 이후 결제 시점에는 자동화된 실행자가 트랜잭션을 전송할 수 있다. 하지만 실행 가능 여부는 Smart Account의 policy 또는 permission module이 매번 검증해야 한다. 이런 session key나 permission module은 ERC-4337 자체가 하나의 방식으로 표준화한 기능이라기보다 wallet-specific validation logic에 가깝고, scope, revocation, validUntil, target, amount cap, executor binding을 각 구현이 책임져야 한다.

이렇게 보면 자동결제에서 외부 시스템이 하는 일은 실행 시점 감지와 트랜잭션 릴레이다. 최종 권한 판단은 Smart Account의 검증 규칙이 한다. 조건을 벗어난 금액, 만료된 policy, 허용되지 않은 executor, 너무 이른 반복 실행은 모두 실패해야 한다.

이 구분이 중요하다. 자동화가 곧 수탁은 아니다. 하지만 자동화가 범용 서버 서명으로 구현되는 순간 custody risk가 생긴다.

MPC 기반 threshold signing도 답이 될 수 있을까?

MPC, 정확히는 threshold signing 또는 MPC 기반 threshold signature도 함께 비교해볼 만한 선택지였다. 2-of-2 구조는 사용자 share와 서버 share가 모두 있어야 서명이 가능하므로, 서버 단독 탈취를 막을 수 있다. 하지만 availability가 좋지 않다. 사용자 기기나 share backup을 잃으면 복구가 어렵고, 서버 장애나 서명 거부도 곧 UX 장애가 된다.

2-of-3은 조금 더 현실적인 구조처럼 보인다. 사용자 기기, 서버, recovery share 또는 guardian으로 나누면 분실 복구 UX는 좋아진다. 하지만 여기서도 질문은 같다.

누가 threshold를 만족시킬 수 있는가?

서버가 두 share를 통제하거나, 계정 복구만으로 recovery share를 사실상 재발급할 수 있다면 다시 custodial에 가까워진다. 반대로 서버가 한 share만 갖고 사용자의 실시간 승인이 필요하다면 non-custodial에 가까워지지만, 자동결제 같은 서버사이드 자동화는 별도 policy 없이는 풀기 어렵다.

MPC는 개인키를 쪼갠다는 점에서 매력적이다. 하지만 non-custodial 여부는 share 개수만으로 결정되지 않는다. 구현 방식, backup 정책, recovery 정책에 따라 같은 2-of-3도 전혀 다른 trust model이 된다. 중요한 것은 누가 threshold를 만족시키는가뿐 아니라, 누가 share를 재발급하거나 recovery 구성을 바꿀 수 있는가이다.

예를 들어 서버가 signing threshold에는 1 share만 참여하더라도, recovery share 재발급이나 guardian 교체를 단독 승인할 수 있다면 실질적 통제력은 다시 서버 쪽으로 기울 수 있다. 반대로 recovery share가 사용자 오프라인 백업이나 독립 guardian에 분산되어 있고 서버가 이를 단독으로 재구성할 수 없다면 같은 2-of-3이라도 훨씬 non-custodial에 가깝다.

Sui zkLogin에서 참고한 점

Sui의 zkLogin도 흥미로운 비교 대상이었다. zkLogin은 OAuth 로그인 경험을 사용하면서도, OAuth 계정만으로는 자산을 움직일 수 없도록 설계되어 있다. Sui 문서는 zkLogin이 익숙한 OAuth login flow로 Sui에서 transaction을 보낼 수 있게 하고, zero-knowledge proof로 OAuth identifier와 on-chain address가 공개적으로 연결되지 않게 한다고 설명한다.

흐름을 단순화하면 ephemeral key pair를 먼저 만들고, OAuth login으로 JWT를 얻고, user salt와 JWT claim을 사용해 주소와 ZK proof를 만든 뒤, ZK proof와 ephemeral signature를 묶어 transaction signature로 전송한다. 특히 중요한 점은 OAuth 계정 하나만으로 충분하지 않다는 것이다. 최근 OAuth login에서 나온 credential과 OAuth provider가 관리하지 않는 salt가 함께 필요하고, transaction 전송 시에는 ZK proof와 ephemeral signature가 검증된다. 이런 점에서 2FA에 가까운 구조로 이해할 수 있지만, Sui의 실행 모델 안에서 성립하는 native primitive라는 점을 분명히 봐야 한다.

여기서 참고할 만한 점은 명확했다.

로그인은 wallet authority가 아니다.
OAuth provider나 백엔드 시스템은 단독으로 거래할 수 없어야 한다.
익숙한 로그인 UX와 non-custodial 구조는 반드시 충돌하지 않는다.

zkLogin은 Sui-native primitive라 EVM 기반 Smart Account에 그대로 가져올 수 없다. 구조적 참고 대상이지, EVM에 그대로 이식할 수 있는 패턴은 아니다. 그래도 로그인, proof, salt, ephemeral key, policy를 분리해 권한을 구성하는 관점은 충분히 참고할 만하다.

동시에 이 모델도 구현 세부사항을 봐야 한다. salt provider, proving service, ephemeral key 보관 방식은 availability, privacy, recovery trust assumption에 영향을 준다. OAuth provider가 단독으로 transaction을 만들 수 없다는 점은 강한 장점이지만, 전체 지갑 UX가 어떤 salt 관리와 복구 정책을 선택하는지는 별도의 threat model로 평가해야 한다.

Proof of Non-Custody는 어디까지 보여줄 수 있을까

Passkey 기반 구조에서는 private key export가 일반적으로 어렵다. 따라서 seed phrase처럼 "내가 키를 갖고 있다"는 것을 직접 보여주는 방식은 맞지 않을 수 있다.

대신 proof of non-custody는 다른 방식으로 생각할 수 있다. 다만 이것은 수학적으로 모든 수탁 가능성을 증명하는 절대적 proof라기보다, 특정 서버 재료만으로 같은 승인을 재현할 수 없다는 운영상 증거에 가깝다.

1. 독립 verifier가 challenge를 만든다.
2. 사용자는 기기에서 WebAuthn ceremony를 수행한다.
3. Smart Account는 해당 assertion 또는 검증값을 확인한다.
4. 서버 측 재료만 가진 공격자는 같은 assertion을 만들 수 없어야 한다.

중요한 것은 key export가 아니다. 서버 없이 사용자가 독립적으로 wallet approval assertion을 만들 수 있고, Smart Account가 그것을 인정한다는 점이다. 반대로 서버 DB, relayer key, paymaster key, 전송 인프라만으로 같은 assertion을 만들 수 없어야 한다.

또 이것만으로 충분하지는 않다. recovery, owner change, upgrade, module install, session key, co-signer 경로 중 하나라도 서버 단독 실행이 가능하면 사용자 결제 서명이 안전하더라도 전체 지갑은 non-custodial이라고 보기 어렵다. 그래서 proof of non-custody는 독립 challenge signing뿐 아니라 negative test와 권한 inventory가 함께 있어야 한다.

결론

좋은 지갑 UX를 제공하려면 백엔드와 인프라는 많은 일을 해야 한다. 지갑 주소를 찾고, fee를 계산하고, bundler와 paymaster를 붙이고, 전송 상태를 관리해야 한다.

하지만 많이 안다는 것과 승인할 수 있다는 것은 다르다.

내가 정리한 non-custodial 지갑 UX의 핵심은 서버를 없애는 것이 아니라, 서버의 권한을 보조와 전송으로 제한하는 것이다.

wallet control은 사용자 기기의 assertion과 Smart Account validation rule이 함께 담당한다.
device recovery는 별도의 Recovery Authority가 담당한다.
server-side automation은 policy / permission module이 담당한다.
백엔드와 인프라는 이 셋을 연결하지만, 어느 것도 단독으로 생성하지 않는다.

이 구조는 일부 편한 UX를 포기한다. 계정 세션만으로 모든 것을 복구해주는 모델은 어렵다. 하지만 그 불편함은 설계 결함이라기보다, non-custodial 경계를 지키기 위해 명시적으로 받아들여야 하는 trade-off에 가깝다.

참고한 문서

다음 읽기

이 생각이 이어지는 방향

Blockchain 더 보기
공유

읽은 뒤의 대화

읽은 뒤의 생각을 이어갑니다

질문, 반론, 조용한 후속 메모를 이 글 아래에 남길 수 있습니다.

sayu.day는 생각과 작업의 흔적을 천천히 정리하는 개인 출판물입니다.
직접 겪고 검토한 내용, 다시 읽을 만한 아이디어, 작업하며 남긴 메모를 모읍니다.
시간이 지난 글은 현재의 판단과 다를 수 있어 업데이트 맥락을 함께 남깁니다.

© 2026 sayu.day