체인의정석

ERC4337 + Pass Key, 계정추상화에 패스키 적용하기 본문

블록체인/퍼블릭 블록체인

ERC4337 + Pass Key, 계정추상화에 패스키 적용하기

체인의정석 2024. 10. 14. 16:15
728x90
반응형

패스키는 일반적인 블록체인상의 검증 방식인 ECDSA서명과 다른 서명 로직이 들어가게 된다.

계정추상화의 계정추상화는 ECDSA서명을 통한 예제를 지원한다. 하지만 각 유저들이 보유하는 SmartAccount에 P256 서명을 검증하는 로직을 만들어야 계정추상화에 패스키까지 적용할 수 있다.

다행히 P256Verifer는 오딧 받은 유명한 공개 라이브러리가 있다. (https://p256.eth.limo/)

mport "p256-verifier/P256.sol";

bytes32 hash; // message hash
uint256 r, s; // signature
uint256 x, y; // public key

bool valid = P256.verifySignature(hash, r, s, x, y);

해당 라이브러리를 사용하여 P256.verifySignature를 하면 되는데, 동일한 분이 작성한 아래 코드에 구현이 되어있다.

https://github.com/daimo-eth/p256-verifier/blob/master/src/WebAuthn.sol

 

p256-verifier/src/WebAuthn.sol at master · daimo-eth/p256-verifier

P256 signature verification solidity contract. Contribute to daimo-eth/p256-verifier development by creating an account on GitHub.

github.com

해당 내용의 입력값을 보기 위하여 테스트 코드를 보았다.

 

https://github.com/daimo-eth/p256-verifier/blob/master/test/WebAuthn.t.sol

 

p256-verifier/test/WebAuthn.t.sol at master · daimo-eth/p256-verifier

P256 signature verification solidity contract. Contribute to daimo-eth/p256-verifier development by creating an account on GitHub.

github.com

 

uint256[2] publicKey = [
        0x80d9326e49eb6314d03f58830369ea5bafbc4e2709b30bff1f4379586ca869d9,
        0x806ed746d8ac6c2779a472d8c1ed4c200b07978d9d8d8d862be8b7d4b7fb6350
    ];
    string clientDataJSON =
        '{"type":"webauthn.get","challenge":"dGVzdA","origin":"https://funny-froyo-3f9b75.netlify.app"}';
    bytes challenge = hex"74657374";
    bytes authenticatorData =
        hex"e0b592a7dd54eedeec65206e031fc196b8e5915f9b389735860c83854f65dc0e1d00000000";
    uint256 r =
        0x32e005a53ae49a96ac88c715243638dd5c985fbd463c727d8eefd05bee4e2570;
    uint256 s =
        0x7a4fef4d0b11187f95f69eefbb428df8ac799bbd9305066b1e9c9fe9a5bcf8c4;
    uint256 challengeLocation = 23;
    uint256 responseTypeLocation = 1;

해당 값이 VerifySignature의 입력값으로 들어갔는데, 
ECDSA서명의 경우 PaymasterAndData에 들어가는 서명하는 원본 메세지가 validUnitil, validAfter, userOp hash였다.
이에 맞추어서 challenge에는 해당 값을 인코딩한 값이 들어가면 될 것이고

나머지 값들의 경우에도 챗지피티로 대략적인 느낌을 찾아보았다. 내용은 다음과 같다.

이 코드에서 사용된 값들은 WebAuthn 인증 과정에서 생성된 실제 데이터로, 웹 브라우저와 인증기(Authenticator) 사이에서 주고받은 정보를 테스트 목적으로 고정된 값으로 사용하고 있습니다. 각 값은 WebAuthn 인증 데이터의 특정 구성 요소를 나타냅니다. 자세히 설명해 드리겠습니다:

  1. publicKey (공개키):
    • 이 값은 P-256 공개키의 두 좌표 (X, Y)를 나타냅니다. WebAuthn에서 공개키는 사용자의 인증 기기에 저장된 개인 키에 해당하는 공개 키입니다.
    • 이 공개키는 credential public key로 사용되며, WebAuthn 검증 중에 서명된 데이터와 대조하여 서명이 올바른지 확인하는 데 사용됩니다.
    • 0x80d9326e...와 0x806ed746...은 타원 곡선의 두 좌표값(X, Y)이며, P-256 타원 곡선에서 유효한 공개키 좌표입니다.
  2. clientDataJSON:
    • 클라이언트 측에서 생성된 JSON 데이터를 나타냅니다. 이 JSON 데이터에는 인증 요청 또는 로그인의 세부 사항이 포함되어 있으며, 이 값은 서명 검증 과정에서 중요한 역할을 합니다.
    • {"type":"webauthn.get","challenge":"dGVzdA","origin":"https://funny-froyo-3f9b75.netlify.app"}는 사용자가 인증하는 중에 발생한 이벤트를 설명합니다.
      • type: webauthn.get은 사용자가 로그인을 시도하고 있다는 것을 나타냅니다.
      • challenge: 이 값은 서버가 제공한 도전값(challenge)으로, 이는 dGVzdA라는 base64로 인코딩된 데이터입니다. 디코딩하면 "test"라는 문자열입니다.
      • origin: 인증 요청을 발생시킨 웹사이트의 URL입니다. 여기서는 https://funny-froyo-3f9b75.netlify.app입니다.
  3. challenge (도전값):
    • 이 값은 실제로 서명 검증 과정에서 사용된 도전값을 바이트 형식으로 나타낸 것입니다. 이는 clientDataJSON에서 설명된 challenge와 일치해야 합니다.
    • 여기서는 hex"74657374"으로, 이는 ASCII로 "test"라는 문자열입니다.
  4. authenticatorData:
    • 이 값은 WebAuthn 인증기(Authenticator)가 생성한 데이터로, 서명에 포함됩니다.
    • authenticatorData에는 사용자의 인증 상태, RP ID 해시, 서명 카운터 등의 정보가 포함됩니다. 이 데이터는 WebAuthn에서 매우 중요한 역할을 합니다.
    • 여기서 사용된 값은 e0b592a7...로, WebAuthn 인증기의 상태를 나타냅니다.
  5. r, s (서명 값):
    • 이 값들은 ECDSA 서명의 두 구성 요소인 r과 s 값을 나타냅니다. WebAuthn에서 개인 키를 사용하여 생성된 서명입니다.
    • r과 s는 타원 곡선 디지털 서명 알고리즘(ECDSA)에서 생성된 값으로, 이 두 값이 서명이 유효한지 검증하는 데 사용됩니다.
    • r과 s의 값은 다음과 같습니다:
      • r: 0x32e005a53ae49a96...
      • s: 0x7a4fef4d0b11187f...
  6. challengeLocationresponseTypeLocation:
    • 이 값들은 clientDataJSON 내에서 도전값(challenge)와 응답 유형(response type)이 위치하는 바이트 오프셋을 나타냅니다.
    • challengeLocation = 23은 JSON 데이터의 23번째 바이트부터 challenge가 시작된다는 것을 의미하며, 이는 dGVzdA로 시작하는 도전값의 위치입니다.
    • responseTypeLocation = 1은 responseType이 JSON 데이터의 첫 번째 바이트에 위치함을 나타내며, 이는 "type": "webauthn.get"의 시작 부분입니다.

이 값들의 생성 과정

이 값들은 WebAuthn 프로토콜을 사용하여 클라이언트와 서버 간의 실제 인증 과정에서 생성되며, 서명 검증을 위해 필요한 데이터입니다. 테스트 코드에서 이러한 값들을 하드코딩하여 실제 WebAuthn 인증의 결과를 모방하여 검증할 수 있도록 설정한 것입니다.


해당 컨트렉트를 통해서 만든 AA지갑의 구현체는 다음과 같다.

https://github.com/passkeys-4337/smart-wallet/blob/f3aa9fd44646fde0316fc810e21cc553a9ed73e0/contracts/src/SimpleAccount.sol

 

smart-wallet/contracts/src/SimpleAccount.sol at f3aa9fd44646fde0316fc810e21cc553a9ed73e0 · passkeys-4337/smart-wallet

Simple implementation of an ERC-4337 contract wallet controlled by Passkeys - passkeys-4337/smart-wallet

github.com

    function _validateSignature(
        bytes memory message,
        bytes calldata signature
    ) private view returns (bool) {
        Signature memory sig = abi.decode(signature, (Signature));

        return
            WebAuthn.verifySignature({
                challenge: message,
                authenticatorData: sig.authenticatorData,
                requireUserVerification: false,
                clientDataJSON: sig.clientDataJSON,
                challengeLocation: sig.challengeLocation,
                responseTypeLocation: sig.responseTypeLocation,
                r: sig.r,
                s: sig.s,
                x: uint256(publicKey.X),
                y: uint256(publicKey.Y)
            });
    }

서명 검증은 다음과 같이 진행하며 되며 여기에 들어가는 signature를 어떤 식으로 대입하였는지 보기 위하여 테스트 코드를 보았다.

https://github.com/passkeys-4337/smart-wallet/blob/f3aa9fd44646fde0316fc810e21cc553a9ed73e0/front/src/libs/smart-wallet/service/userOps/builder.ts#L140

 

smart-wallet/front/src/libs/smart-wallet/service/userOps/builder.ts at f3aa9fd44646fde0316fc810e21cc553a9ed73e0 · passkeys-4337

Simple implementation of an ERC-4337 contract wallet controlled by Passkeys - passkeys-4337/smart-wallet

github.com

해당 코드를 보면

    // get userOp hash (with signature == 0x) by calling the entry point contract
    const userOpHash = await this._getUserOpHash(userOp);

    // version = 1 and validUntil = 0 in msgToSign are hardcoded
    const msgToSign = encodePacked(["uint8", "uint48", "bytes32"], [1, 0, userOpHash]);

    // get signature from webauthn
    const signature = await this.getSignature(msgToSign, keyId);

userOp 해시를 먼저 구한 후에 version, validUntil, userOpHash를 넣어서 서명할 메세지를 만들었다.

https://github.com/FuelLabs/authn-sign

 

GitHub - FuelLabs/authn-sign: ✍️ authn-sign - A simplified browser interface for WebAuthn focused on secp256r1 (P-256).

✍️ authn-sign - A simplified browser interface for WebAuthn focused on secp256r1 (P-256). - FuelLabs/authn-sign

github.com

위의 깃허브를 사용하면 자바스크립트로 P256서명을 만들어서 사용할 수 있다.

위와 같은 서명로직을 이용하여 오프체인에서 입력값만 잘 뽑아내면 여기서 잘 풀리겠지만 문제는 해당 부분의 작동하는 실 구현체를 찾기는 어려운것 같아 직접 구현해야 할 것 같다.

참고할만한 글

https://www.stackup.sh/blog/passkeys-webauthn-erc4337

 

How to Use Passkeys on Ethereum

Learn how to use passkeys and WebAuthn on the Ethereum Blockchain with account abstraction.

www.stackup.sh

구현 예제

https://github.com/passkeys-4337/smart-wallet

 

GitHub - passkeys-4337/smart-wallet: Simple implementation of an ERC-4337 contract wallet controlled by Passkeys

Simple implementation of an ERC-4337 contract wallet controlled by Passkeys - passkeys-4337/smart-wallet

github.com

P256 서명 컨트렉트

https://p256.eth.limo/

 

P256Verifier

P256Verifier:  0xc2b78104907F722DABAc4C69f826a522B2754De4   Verify  Secure Enclave signatures on-chain. Deployments ↓Find on-chain deployments.Usage ↓See Solidity usage code.Blog ->Learn more.

p256.eth.limo

 

728x90
반응형
Comments