diff --git a/.gitignore b/.gitignore index 04babad..9e22afd 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,5 @@ out/ .DS_Store ### .env ### -.env \ No newline at end of file +.env +gradle.properties \ No newline at end of file diff --git a/did-agent b/did-agent deleted file mode 160000 index 8906266..0000000 --- a/did-agent +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8906266b20b0045cde2d8631ae01474370c66b04 diff --git a/src/main/java/com/doubleo/didagent/global/util/Ed25519KeyGenerator.java b/src/main/java/com/doubleo/didagent/global/util/Ed25519KeyGenerator.java index 31737e2..aac9d75 100644 --- a/src/main/java/com/doubleo/didagent/global/util/Ed25519KeyGenerator.java +++ b/src/main/java/com/doubleo/didagent/global/util/Ed25519KeyGenerator.java @@ -2,71 +2,146 @@ import java.security.KeyPair; import java.security.KeyPairGenerator; -import java.security.interfaces.EdECPrivateKey; -import java.security.interfaces.EdECPublicKey; +import java.security.Security; import java.util.Arrays; import org.bitcoinj.core.Base58; -import org.bouncycastle.jcajce.interfaces.XDHPrivateKey; -import org.bouncycastle.jcajce.interfaces.XDHPublicKey; +import org.bouncycastle.jce.provider.BouncyCastleProvider; public class Ed25519KeyGenerator { + static { + // BouncyCastle Provider 등록 (X25519 지원을 위해 필요) + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } + public static KeyMaterial generate() throws Exception { - KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519"); - KeyPair kp = kpg.generateKeyPair(); + // Ed25519 키 쌍 생성 + KeyPairGenerator ed25519Kpg = KeyPairGenerator.getInstance("Ed25519"); + KeyPair ed25519Kp = ed25519Kpg.generateKeyPair(); + + // Ed25519 Public Key 처리 + byte[] ed25519PubSpki = ed25519Kp.getPublic().getEncoded(); + byte[] ed25519RawPub = + Arrays.copyOfRange( + ed25519PubSpki, ed25519PubSpki.length - 32, ed25519PubSpki.length); + + // Ed25519 Private Key 처리 + byte[] ed25519PrivPkcs8 = ed25519Kp.getPrivate().getEncoded(); + byte[] ed25519RawPriv = + Arrays.copyOfRange( + ed25519PrivPkcs8, ed25519PrivPkcs8.length - 32, ed25519PrivPkcs8.length); - byte[] pubSpki = kp.getPublic().getEncoded(); - byte[] rawPub = Arrays.copyOfRange(pubSpki, pubSpki.length - 32, pubSpki.length); + // Ed25519 Public Key multicodec encoding (0xED 0x01) + byte[] ed25519PubPrefixed = new byte[ed25519RawPub.length + 2]; + ed25519PubPrefixed[0] = (byte) 0xED; + ed25519PubPrefixed[1] = 0x01; + System.arraycopy(ed25519RawPub, 0, ed25519PubPrefixed, 2, ed25519RawPub.length); - byte[] privPkcs8 = kp.getPrivate().getEncoded(); - byte[] rawPriv = Arrays.copyOfRange(privPkcs8, privPkcs8.length - 32, privPkcs8.length); + String ed25519PublicKeyBase58 = "z" + Base58.encode(ed25519PubPrefixed); + String ed25519PrivateKeyBase58 = Base58.encode(ed25519RawPriv); - byte[] prefixed = new byte[rawPub.length + 2]; - prefixed[0] = (byte) 0xED; - prefixed[1] = 0x01; - System.arraycopy(rawPub, 0, prefixed, 2, rawPub.length); + // X25519 키 쌍 생성 (별도의 독립적인 키 쌍) + KeyPairGenerator x25519Kpg = KeyPairGenerator.getInstance("X25519", "BC"); + KeyPair x25519Kp = x25519Kpg.generateKeyPair(); - String publicKeyBase58 = "z" + Base58.encode(prefixed); - String privateKeyBase58 = Base58.encode(rawPriv); + // X25519 Public Key 처리 + byte[] x25519PubEncoded = x25519Kp.getPublic().getEncoded(); + byte[] x25519RawPub = extractX25519PublicKey(x25519PubEncoded); - KeyPairGenerator xKpg = KeyPairGenerator.getInstance("X25519", "BC"); - KeyPair xKp = xKpg.generateKeyPair(); + // X25519 Private Key 처리 + byte[] x25519PrivEncoded = x25519Kp.getPrivate().getEncoded(); + byte[] x25519RawPriv = extractX25519PrivateKey(x25519PrivEncoded); - byte[] xPubSpki = xKp.getPublic().getEncoded(); - byte[] xRawPub = Arrays.copyOfRange(xPubSpki, xPubSpki.length - 32, xPubSpki.length); + // X25519 Public Key multicodec encoding (0xEC 0x01) + byte[] x25519PubPrefixed = new byte[x25519RawPub.length + 2]; + x25519PubPrefixed[0] = (byte) 0xEC; + x25519PubPrefixed[1] = 0x01; + System.arraycopy(x25519RawPub, 0, x25519PubPrefixed, 2, x25519RawPub.length); - /* multicodec: 0xEC 0x01 = X25519 public key */ - byte[] xPrefixed = new byte[xRawPub.length + 2]; - xPrefixed[0] = (byte) 0xEC; - xPrefixed[1] = 0x01; - System.arraycopy(xRawPub, 0, xPrefixed, 2, xRawPub.length); + String x25519PublicKeyMb58 = "z" + Base58.encode(x25519PubPrefixed); - String agreementKeyMb58 = "z" + Base58.encode(xPrefixed); - byte[] xPrivSpki = xKp.getPrivate().getEncoded(); - // PKCS#8 또는 SPKI 형식으로 인코딩된 값이 넘어오므로, 끝 32바이트가 실제 raw private - byte[] xRawPriv = Arrays.copyOfRange(xPrivSpki, xPrivSpki.length - 32, xPrivSpki.length); + // X25519 Private Key multicodec encoding (0x82 0x26) + byte[] x25519PrivPrefixed = new byte[x25519RawPriv.length + 2]; + x25519PrivPrefixed[0] = (byte) 0x82; + x25519PrivPrefixed[1] = 0x26; + System.arraycopy(x25519RawPriv, 0, x25519PrivPrefixed, 2, x25519RawPriv.length); - // multicodec 형식 붙이기 (0xEC 0x01 = X25519 private multicodec) - byte[] xPrivPrefixed = new byte[xRawPriv.length + 2]; - xPrivPrefixed[0] = (byte) 0xEC; - xPrivPrefixed[1] = 0x01; - System.arraycopy(xRawPriv, 0, xPrivPrefixed, 2, xRawPriv.length); + String x25519PrivateKeyMb58 = "z" + Base58.encode(x25519PrivPrefixed); - // 최종적으로 multibase58 (z-prefixed) 문자열 - String x25519PrivateMb58 = "z" + Base58.encode(xPrivPrefixed); + // 디버깅 출력 + System.out.println("=== Ed25519 Keys ==="); + System.out.println("Ed25519 Public (hex): " + bytesToHex(ed25519RawPub)); + System.out.println("Ed25519 Private (hex): " + bytesToHex(ed25519RawPriv)); + + System.out.println("\n=== X25519 Keys ==="); + System.out.println("X25519 Public (hex): " + bytesToHex(x25519RawPub)); + System.out.println("X25519 Private (hex): " + bytesToHex(x25519RawPriv)); + + System.out.println("\n=== Encoded Keys ==="); + System.out.println("Ed25519 Public MB58: " + ed25519PublicKeyBase58); + System.out.println("Ed25519 Private B58: " + ed25519PrivateKeyBase58); + System.out.println("X25519 Public MB58: " + x25519PublicKeyMb58); + System.out.println("X25519 Private MB58: " + x25519PrivateKeyMb58); - /* 반환 객체에 추가로 포함 */ return new KeyMaterial( - rawPub, - rawPriv, - publicKeyBase58, - privateKeyBase58, - agreementKeyMb58, // NEW: X25519 public multibase - (EdECPublicKey) kp.getPublic(), - (EdECPrivateKey) kp.getPrivate(), - (XDHPublicKey) xKp.getPublic(), - (XDHPrivateKey) xKp.getPrivate(), - x25519PrivateMb58); + ed25519RawPub, // rawPub (Ed25519) + ed25519RawPriv, // rawPriv (Ed25519) + ed25519PublicKeyBase58, // signingKeyMb58 (Ed25519 public) + ed25519PrivateKeyBase58, // signingPrivBase58 (Ed25519 private) + x25519PublicKeyMb58, // agreementKeyMb58 (X25519 public) + x25519PrivateKeyMb58 // x25519PrivateMb58 (X25519 private) + ); + } + + /** + * X25519 Public Key에서 원시 32바이트 추출 SPKI 형식: 30 2A 30 05 06 03 2B 65 6E 03 21 00 [32바이트 public + * key] + */ + private static byte[] extractX25519PublicKey(byte[] encoded) { + // SPKI 형식에서 마지막 32바이트가 실제 public key + if (encoded.length < 32) { + throw new IllegalArgumentException("Invalid X25519 public key encoding"); + } + return Arrays.copyOfRange(encoded, encoded.length - 32, encoded.length); + } + + /** X25519 Private Key에서 원시 32바이트 추출 PKCS#8 형식에서 실제 private key 데이터 추출 */ + private static byte[] extractX25519PrivateKey(byte[] encoded) { + // PKCS#8 형식 파싱 + // 30 2E 02 01 00 30 05 06 03 2B 65 6E 04 22 04 20 [32바이트 private key] + + if (encoded.length < 32) { + throw new IllegalArgumentException("Invalid X25519 private key encoding"); + } + + // PKCS#8에서 private key는 OCTET STRING 내부에 있음 + // 일반적으로 마지막 32바이트가 실제 private key + // 하지만 더 정확한 파싱을 위해 OCTET STRING을 찾음 + + for (int i = 0; i < encoded.length - 34; i++) { + if (encoded[i] == 0x04 + && encoded[i + 1] == 0x22 + && encoded[i + 2] == 0x04 + && encoded[i + 3] == 0x20) { + // 0x04 0x22 = OCTET STRING (34 bytes) + // 0x04 0x20 = OCTET STRING (32 bytes) - actual private key + return Arrays.copyOfRange(encoded, i + 4, i + 36); + } + } + + // fallback: 마지막 32바이트 사용 + return Arrays.copyOfRange(encoded, encoded.length - 32, encoded.length); + } + + /** 바이트 배열을 hex 문자열로 변환 */ + private static String bytesToHex(byte[] bytes) { + StringBuilder result = new StringBuilder(); + for (byte b : bytes) { + result.append(String.format("%02x", b)); + } + return result.toString(); } } diff --git a/src/main/java/com/doubleo/didagent/global/util/KeyMaterial.java b/src/main/java/com/doubleo/didagent/global/util/KeyMaterial.java index 6253541..9e4deb7 100644 --- a/src/main/java/com/doubleo/didagent/global/util/KeyMaterial.java +++ b/src/main/java/com/doubleo/didagent/global/util/KeyMaterial.java @@ -1,18 +1,13 @@ package com.doubleo.didagent.global.util; -import java.security.interfaces.EdECPrivateKey; -import java.security.interfaces.EdECPublicKey; -import org.bouncycastle.jcajce.interfaces.XDHPrivateKey; -import org.bouncycastle.jcajce.interfaces.XDHPublicKey; - public record KeyMaterial( byte[] rawEd25519Public, // 32-byte 원본 Ed25519 공개키 byte[] rawEd25519Private, // 32-byte 원본 Ed25519 비밀키 String signingKeyMb58, // 멀티코덱+멀티베이스(“z…”) Ed25519 공개키 String signingPrivBase58, // Base58 인코딩된 Ed25519 비밀키(원본 32 바이트) String agreementKeyMb58, // 멀티코덱+멀티베이스(“z…”) X25519 공개키 - EdECPublicKey signingPublic, // JCA Ed25519 PublicKey (서명용) - EdECPrivateKey signingPrivate, // JCA Ed25519 PrivateKey (서명용) - XDHPublicKey agreementPublic, // JCA X25519 PublicKey (암호화·키합의용) - XDHPrivateKey agreementPrivate, // JCA X25519 PrivateKey (암호화·키합의용) + // EdECPublicKey signingPublic, // JCA Ed25519 PublicKey (서명용) + // EdECPrivateKey signingPrivate, // JCA Ed25519 PrivateKey (서명용) + // XDHPublicKey agreementPublic, // JCA X25519 PublicKey (암호화·키합의용) + // XDHPrivateKey agreementPrivate, // JCA X25519 PrivateKey (암호화·키합의용) String x25519PrivateMb58) {} diff --git a/src/main/java/com/doubleo/didagent/global/util/PeerDidUtil.java b/src/main/java/com/doubleo/didagent/global/util/PeerDidUtil.java index a8b32c4..9bf08be 100644 --- a/src/main/java/com/doubleo/didagent/global/util/PeerDidUtil.java +++ b/src/main/java/com/doubleo/didagent/global/util/PeerDidUtil.java @@ -19,14 +19,15 @@ public static String createPeerDid2( String enc2 = "E" + agreementKeyMb; // key-agreement key List routingKeysDidUrl = PeerDidUtil.convertRoutingKeys(routingKeys); JSONObject svc = new JSONObject(); - svc.put("id", "#didcomm-0"); - svc.put("t", "did-communication"); - svc.put("p", 0); + + svc.put("type", "DIDComm"); + svc.put("priority", 0); + svc.put("id", "#service-0"); svc.put("recipientKeys", new JSONArray().put("#key-1")); if (routingKeys != null && !routingKeys.isEmpty()) { - svc.put("r", new JSONArray(routingKeysDidUrl)); // routingKeys → r + svc.put("routingKeys", new JSONArray(routingKeysDidUrl)); // routingKeys → r } - svc.put("s", serviceEndpoint); // serviceEndpoint → s + svc.put("serviceEndpoint", serviceEndpoint); // serviceEndpoint → s String enc3 = "S" @@ -45,7 +46,7 @@ private static String rawVerkeyToDidKey(String verkeyBase58) { System.arraycopy(raw, 0, prefixed, 2, raw.length); String multibase = "z" + Base58.encode(prefixed); - return "did:key:" + multibase + "#" + multibase; + return "did:key:" + multibase; } private static List convertRoutingKeys(List rawKeys) {