MynaAgent 개발: 마이넘버 카드 연령 증명부터 JPYC 지급까지
요약
MynaAgent는 지갑 내에서 작동하는 AI 에이전트로, 사용자가 급여 지급 조건을 검색하고 신청하는 과정을 자동화합니다. 이 과정은 Zero-Knowledge Proof(ZKP) 기반의 연령 증명과 블록체인 컨트랙트를 결합하여 구현되었으며, 개인 정보 노출 없이 자격 검증 및 토큰 지급이 이루어집니다.
핵심 포인트
- AI 에이전트가 급여 검색부터 신청까지 자동화합니다.
- NFC 카드와 ZKP를 이용해 연령 증명(20세 이상)을 수행합니다.
- 개인 정보(생년월일 등) 노출 없이 자격 검증 및 토큰 지급이 가능합니다.
- ProveKit과 Groth16 조합으로 효율적인 온체인 검증 환경을 구축했습니다.
ETHGlobal Tokyo 2026에서, MynaWallet 내에서 작동하는 AI 에이전트인 MynaAgent를 만들었습니다. '받을 수 있는 급여를 찾아줘'라고 요청하면, 급여 조건을 조사하고 본인의 동의를 얻어 신청을 진행합니다.
데모로 준비한 것은 20세 이상을 조건으로 하는 500 JPYC 지급입니다. 사용자가 iPhone에 카드를 갖다 대면, iPhone이 연령의 영지식 증명(Zero-Knowledge Proof)을 만듭니다. Polygon Amoy 상의 컨트랙트가 그 증명을 검증하고, JPYC를 지급합니다. Amoy는 테스트넷이며, JPYC 역시 테스트 토큰입니다.
저는 ZK 기반 연령 증명, 컨트랙트, verifier 부분을 담당했습니다. 작품 페이지와 공개 코드에 데모와 구현을 정리해 두었습니다.
AI가 급여를 찾고, 지갑이 카드를 읽다
급여 지급 대상이 되어 있어도, 제도를 모르거나 신청을 잊으면 받을 수 없습니다. MynaAgent에서는 급여를 찾아 신청하는 과정을 지갑 내 채팅에서 시작할 수 있도록 했습니다.
Claude는 급여 검색, 신청서 작성, 증명 생성 요청, 증명 제출 등을 도구를 통해 진행합니다. '20세 이상'이라는 조건은 카드로부터 만든 증명을 컨트랙트가 검증하여 확인합니다.
사용자가 동의하면, 지급 창구의 Worker가 신청과 챌린지를 만듭니다. MynaWallet이 동의 화면을 보여주고, 사용자는 PIN을 입력하고 카드를 iPhone에 갖다 댑니다. MynaWallet이 NFC로 카드를 읽어, iPhone 상에서 증명을 생성합니다.
증명은 미니 앱(mini-app)이 가지고 있다가 Worker에게 제출합니다. 미니 앱은 LLM에게 증명 생성 성공 여부만 반환합니다. 컨트랙트로 트랜잭션을 보내는 것은 Worker의 operator 키입니다. LLM에는 비밀키나 지급 자금을 전달하지 않습니다.
지갑과 미니 앱의 코드는 공개 리포지토리에 포함되어 있지 않습니다. 단말기 측 흐름은 공개 README의 구성도와 데모에서 확인할 수 있습니다.
생년월일을 보내는 대신, 20세 이상임을 증명하다
연령 증명에는 ZeroKeyMate로 만들었던 Noir의 jpki_age 회로와 단말기상의 prover를 사용했습니다. 이 회로는 카드 서명용 인증서가 J-LIS의 루트 키로 서명되어 있고 프로파일에 맞는지, 카드가 신청 챌린지에 서명했는지, 생년월일로부터 20세 이상인지를 검증합니다.
지급 창구에 보내는 것은 증명과 공개 입력(public input)입니다. 공개 입력에는 신청의 해시, nonce, 루트 키의 해시, 기준 시각, 유효 기간을 넣습니다. 생년월일이나 카드의 인증서는 지급 창구나 온체인으로 전달하지 않습니다.
카드가 로그인 중인 본인의 것인지 여부는 별도로 확인합니다. 같은 NFC 세션에서 만드는 다른 카드 서명과 서명용 인증서를 MynaWallet의 백엔드로 보내, JPKI로 확인하여 본인의 기록과 대조합니다. 인증서는 지급 창구와 체인에서는 받지 않지만, MynaWallet의 백엔드는 받습니다.
ProveKit으로 Groth16을 사용한 이유
증명 생성 및 검증에는 World Foundation의 ProveKit을 사용했고, 백엔드에는 Groth16을 선택했습니다. iPhone에서 만든 증명을 Solidity 컨트랙트로 검증하고, 같은 트랜잭션으로 JPYC를 지급하는 부분까지 이 조합으로 동작시킬 수 있었기 때문입니다.
ProveKit에는 다음 3가지 작업을 맡겼습니다.
prepare로 Noir 회로를 R1CS에 변환하여 Groth16의 증명 키와 검증 키를 만듭니다. - ProveKit의 prover를 Rust 라이브러리로 빌드하고, MynaWallet의 Expo 모듈에서 호출하여 iPhone 상에서 증명을 만듭니다.export-solidity로 검증 키에서 Solidity verifier를 생성합니다. 생성된 verifier는 Polygon Amoy에 배포했습니다.
지급 1건의 claim()에는 669,349 gas가 소요되며, 그중 증명 검증은 포크상의 추정치로 약 37만 gas입니다.
사용된 것은 Groth16과 BSB22 커밋먼트의 Solidity verifier를 추가한 PR #447의 revision dd237e5입니다. 이 PR은 작성 시점에도 머지되지 않았으며, 상위 main에는 포함되어 있지 않습니다.
이 버전의 Groth16은 회로 내에서 체인지를 생성하기 위해, 비밀 witness에 대한 Pedersen 커밋먼트(BSB22)를 증명에 포함합니다. 이 커밋먼트에는 난수 마스크가 들어있지 않았습니다. gnark에서는 같은 형태의 커밋먼트로부터 witness 값을 무차별 대입으로 추측할 수 있는 문제가 보고되었습니다 (GHSA-9xcg-3q8v-7fq6).
나이 회로는 생년월일을 비밀 입력으로 가지고 있기 때문에, ZeroKeyMate에서 gnark 수정 사항을 따라 패치를 작성하여 적용했습니다. 이 패치는 커밋먼트 대상에 난수 wire를 1개 추가하고, 증명을 만들 때마다 새로운 난수를 넣습니다. 세트업(setup)에서는 이 wire의 기저가 0이 아님을 확인합니다. 배포된 verifier 역시 이 패치를 적용한 상태로 생성했습니다.
WHIR를 사용하지 않은 이유
ZeroKeyMate에서 동일한 jpki_age 회로와 공개 합성 데이터로, 같은 revision의 WHIR와 Groth16을 비교했습니다. Apple M5 Mac에서 Rayon 스레드를 2개로 고정하고 각각 2번씩 시도했습니다.
| 백엔드 | 증명 | 검증 | 증명 파일 |
|---|---|---|---|
| WHIR | 6.2초 / 5.8초 | 0.73초 / 0.72초 | 약 3.3 MB |
| Groth16 | 10.2초 / 9.7초 | 0.18초 / 0.19초 | 299 bytes |
증명 시간은 CLI 실행부터 키 로딩, witness 계산, 파일 쓰기까지를 포함합니다. 파일 크기는 ProveKit의 압축 형식이며, EVM에 전달하는 384 bytes 형식과는 별개입니다.
Mac 측정에서는 WHIR가 증명이 더 빨랐지만, 이 버전에는 WHIR의 증명을 EVM에서 검증할 수 있는 수단이 없었습니다. EVM에서 검증하려면, WHIR의 검증을 gnark의 재귀 회로(recursive circuit)로 수행하고, 그 결과를 Groth16의 증명에 포장해야 합니다.
포장하는 이유 중 하나는 노드가 받아들일 거래(transaction) 크기에 상한선이 있기 때문입니다. 컨트랙트 인자의 타입에는 상한선이 없습니다. Polygon의 지원 문서에 따르면, 프로토콜에도 하나의 거래 데이터량에 고정된 상한선이 있습니다.
WHIR의 비교는 Mac과 Simulator 상의 합성 데이터로 수행되었습니다. iPhone 실기에서의 WHIR 속도와 실제 카드의 데이터에 대한 WHIR의 영지식성(zero-knowledge)은 확인하지 못했습니다. 비교 절차는 WHIR 비교 기록에, 측정값은 issue #54에 남겨두었습니다.
증명을 다른 지급에 재사용할 수 없도록 함
연령 증명은 창구에서 발행한 신청과 연결됩니다. BenefitOffice.sol은 다음 값들로부터 claimHash를 계산합니다.
return keccak256(abi.encode(
block.chainid,
address(this),
...
체인, 창구, 지급, 수령자, 금액, 연령 조건을 포함했습니다. 컨트랙트 자체가 등록된 지급 내용으로부터 계산하기 때문에, 신청자가 원하는 해시를 전달하여 지급처나 금액을 변경할 수는 없습니다.
BenefitAgeGate.sol에서는 이 해시와 nonce가 공개 입력과 일치하는지 확인합니다. 유효 기간은 15분입니다. 증명의 루트 키가 수락하는 키 중 하나임을, verifier의 코드 해시가 고정된 값과 일치함을 확인한 후에 Groth16 검증을 호출합니다.
이번 회로에서 사용할 연령 조건은 만 20세 이상으로 고정했습니다.
검증과 JPYC 전송을 같은 트랜잭션에서 수행
Worker는 eth_call로 증명을 확인하고, claim()을 시뮬레이션한 후 전송합니다. 지급 완료 여부, 자금 부족, 증명 부정 등의 상황을 전송하기 전에 사용자에게 반환하기 위함입니다.
컨트랙트의 claim()에서도 operator로부터 호출되었는지, 지급이 등록되어 있는지, 수령자가 이미 수급했는지를 확인합니다. 연령 증명을 검증하고, 수급 완료 플래그를 업데이트하며, JPYC를 전송합니다.
전송에 실패하면 전체가 revert됩니다. 수급 완료 플래그만 업데이트되고 사용자가 받을 수 없는 상태를 방지하기 위함입니다.
이중 지급을 막는 기록은 benefitId와 지갑 주소의 조합입니다. 인물 단위의 nullifier는 회로에 없습니다. 1인당 1회로 처리하려면, MynaWallet이 카드 1장에 지갑 주소 1개를 할당하고 백엔드에서의 본인 확인이 전제되어야 합니다.
데모 촬영을 다시 하기 위해 owner가 수급 완료 플래그를 지우는 resetPaid도 포함했습니다. owner가 동일한 수령자에게 재지급을 허용할 수 있기 때문에, 실제 지급 창구에는 넣지 않은 기능입니다.
실기에서 확인한 것과 남아있는 과제
현행 v2의 데모는 JPKI 테스트 카드로 구동했습니다. 실제 마이넘버 카드에 의한 일련의 작업은 이전 verifier로 확인했으며, v2에서의 실카드 지급은 미확인입니다. 테스트 카드와 실카드는 gate가 수락하는 루트 키로 분리되어 있습니다.
측정 기록에서는 iPhone 16 Pro에서 증명 생성에 약 12.4초가 걸렸습니다. 이는 단발적인 관찰입니다. 증명은 384 bytes이지만, 기기에 준비해야 하는 proving key는 653 MiB가 있습니다. 공개 GIF는 1.5배속으로, 증명 생성 대기 시간도 단축했습니다.
Groth16의 setup은 single-party이며, 사용된 ProveKit의 실험 브랜치를 포함하여 미감사 상태입니다. 증명이나 온체인 gate는 증서의 만료를 검사하지 않으며, MynaWallet의 백엔드 측 JPKI 확인에 의존합니다. 현재 제약 사항에 기재되어 있습니다.
이번에 연결한 것은 iOS와 Amoy상의 데모 지급 창구입니다. 지역 지급에 사용하려면 거주지 등의 조건도 필요합니다. 거주지 조건 추가와 실카드에서의 v2 확인은 향후 과제입니다.
논의 (Discussion)
AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기