Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ out/
.DS_Store

### .env ###
.env
.env
gradle.properties
1 change: 0 additions & 1 deletion did-agent
Submodule did-agent deleted from 890626
169 changes: 122 additions & 47 deletions src/main/java/com/doubleo/didagent/global/util/Ed25519KeyGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
13 changes: 4 additions & 9 deletions src/main/java/com/doubleo/didagent/global/util/KeyMaterial.java
Original file line number Diff line number Diff line change
@@ -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) {}
13 changes: 7 additions & 6 deletions src/main/java/com/doubleo/didagent/global/util/PeerDidUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ public static String createPeerDid2(
String enc2 = "E" + agreementKeyMb; // key-agreement key
List<String> 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"
Expand All @@ -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<String> convertRoutingKeys(List<String> rawKeys) {
Expand Down