Ed25519: 바운시캐슬을 사용하여 헬퍼 메소드 작성하기

Dohyeop Kim·2023년 1월 7일
0
post-thumbnail

비트코인과 이더리움은 디지털 서명을 위해서 Secp256k1 타원곡선을 사용하지만, 솔라나(Solana)네트워크는 Ed25519를 사용하는것을 알게되었습니다.
Ed25519는 Curve25519 타원곡선을 기반으로 하고 있습니다.
그래서 기존에 의존성으로 사용중이던 바운시캐슬을 사용하여 키페어 생성과 서명을 수행하는 간단한 헬퍼 메소드를 만들어보았습니다.

소스는 자바 1.8 버전으로 작성되었습니다.

1. 키페어 생성

public static List<byte[]> generateKeypair() {
    final Ed25519KeyGenerationParameters parameters = new Ed25519KeyGenerationParameters(new SecureRandom());
    final Ed25519KeyPairGenerator generator = new Ed25519KeyPairGenerator();
    generator.init(parameters);

    final AsymmetricCipherKeyPair keyPair = generator.generateKeyPair();

    final Ed25519PrivateKeyParameters privateKey = (Ed25519PrivateKeyParameters) keyPair.getPrivate();
    final Ed25519PublicKeyParameters publicKey = (Ed25519PublicKeyParameters) keyPair.getPublic();

    return Arrays.asList(publicKey.getEncoded(), privateKey.getEncoded());
  }

publicKey는 말그대로 외부에 공개할 수 있는 공개키를 의미하고, privateKey는 공개해선 안되는 비밀키를 의미합니다.

2. 서명

  public static byte[] sign(byte[] privateKey, byte[] message) {
    final Ed25519PrivateKeyParameters priKey = new Ed25519PrivateKeyParameters(privateKey);
    Ed25519Signer signer = new Ed25519Signer();
    signer.init(true, priKey);
    signer.update(message, 0, message.length);

    return signer.generateSignature();
  }

privateKeygenerateKeypair 메소드에서 생성한 비밀키를 의미하고, message는 서명의 대상이되는 값으로써, 거래(transaction)의 해시값을 의미할 수 도 있습니다.
이 메소드가 반환하는 값은 서명 그 자체의 값입니다.

3. 검증

  public static boolean verify(byte[] signature, byte[] publicKey, byte[] message) {
    final Ed25519PublicKeyParameters keyParameters = new Ed25519PublicKeyParameters(publicKey);
    final Ed25519Signer signer = new Ed25519Signer();
    signer.init(false, keyParameters);
    signer.update(message, 0, message.length);

    return signer.verifySignature(signature);
  }

signaturesign 메소드에서 생성한 서명값을 의미하고, publicKeygenerateKeypair 메소드에서 생성한 공개키를 의미합니다.
전달받은 message, signature 그리고 publicKey로 서명이 유효한지 검증합니다.

4. 테스트

4.1 서명생성

  @Test
  void verifySuccessTest() {
    // given
    List<byte[]> keypair = Ed25519Helper.generateKeypair();
    byte[] pubKey = keypair.get(0);
    byte[] privKey = keypair.get(1);

    String transaction = "send 10 coins to Bob";
    byte[] message = transaction.getBytes();

    // when
    byte[] sig = sign(privKey, message);

    // then
    boolean verify = verify(sig, pubKey, message);
    assertTrue(verify);
  }

키페어 생성 후, 메시지를 서명하였습니다. 그리고 그 메시지와, 서명 그리고 공개키를 전달 받은 사람이 해당 서명이 유효한지 검증하여 유효한 서명과 메시지로 테스트를
통과했습니다.

4.2 서명자 불일치

  @Test
  void wrongSignerTest() {
    // given
    List<byte[]> keypair = Ed25519Helper.generateKeypair();
    byte[] pubKey = keypair.get(0);

    String transaction = "send 10 coins to Bob";
    byte[] message = transaction.getBytes();

    // when
    byte[] anotherPrivKey = Ed25519Helper.generateKeypair().get(1);
    byte[] sig = sign(anotherPrivKey, message);

    // then
    boolean verify = verify(sig, pubKey, message);
    assertFalse(verify);
  }

위의 경우에는, 전달받은 서명값과 메시지는 기존에 알고있던 공개키를 생성한 사람이 서명한 것이 아닙니다. 따라서 폐기합니다.

4.3 메시지 변경

  @Test
  void modifiedMessageTest() {
    // given
    List<byte[]> keypair = Ed25519Helper.generateKeypair();
    byte[] pubKey = keypair.get(0);
    byte[] privKey = keypair.get(1);

    String transaction = "send 10 coins to Bob";
    byte[] message = transaction.getBytes();

    byte[] sig = sign(privKey, message);

    // when
    byte[] corruptedMessage = "send 10 coins to bob".getBytes();

    // then
    boolean verify = verify(sig, pubKey, corruptedMessage);
    assertFalse(verify);
  }

위의 경우에는, 전달받은 메시지가 서명자가 서명할 당시의 메시지 값과 다릅니다(Bob -> bob). 따라서 폐기합니다.

5. 기타

상기 예제에서는 키페어 생성을 위해 java.security.SecureRandom를 사용하였습니다. 이는 java.util.Random보다 보안성이 높은 난수 생성기이지만,
실무에서 사용하실때는 이것이 보안 요구사항을 만족하는 엔트로피를 생성할 수 있는지 충분한 검토가 필요합니다.

profile
맘마가 먹고십흔 고양이

0개의 댓글