• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FIWARE / contract-management / #95

10 Jun 2026 09:19AM UTC coverage: 1.884% (+0.2%) from 1.724%
#95

push

web-flow
Merge pull request #25 from FIWARE/ticket-44/work

Generate holder did if not provided

53 of 57 new or added lines in 3 files covered. (92.98%)

1 existing line in 1 file now uncovered.

660 of 35030 relevant lines covered (1.88%)

0.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.92
/src/main/java/org/fiware/iam/did/DidKeyGenerator.java
1
package org.fiware.iam.did;
2

3
import org.bouncycastle.jce.ECNamedCurveTable;
4
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
5
import org.bouncycastle.math.ec.ECPoint;
6

7
import java.math.BigInteger;
8
import java.net.URI;
9
import java.security.PrivateKey;
10
import java.security.interfaces.ECPrivateKey;
11

12
/**
13
 * Generates {@code did:key} DIDs from Java {@link PrivateKey} objects following the
14
 * <a href="https://w3c-ccg.github.io/did-key-spec/">W3C did:key specification</a>.
15
 *
16
 * <p>The generation process:</p>
17
 * <ol>
18
 *   <li>Derive the public key from the private key scalar and curve generator point.</li>
19
 *   <li>Compress the public key to its canonical compressed EC point encoding.</li>
20
 *   <li>Prepend the appropriate multicodec unsigned varint prefix.</li>
21
 *   <li>Base58-BTC encode the prefixed bytes.</li>
22
 *   <li>Prepend the multibase {@code z} prefix and the {@code did:key:} scheme.</li>
23
 * </ol>
24
 *
25
 * <p>Supported key types:</p>
26
 * <ul>
27
 *   <li>EC P-256 (secp256r1)</li>
28
 *   <li>EC P-384 (secp384r1)</li>
29
 * </ul>
30
 */
31
public final class DidKeyGenerator {
32

33
    /** The {@code did:key} URI scheme prefix. */
34
    private static final String DID_KEY_PREFIX = "did:key:";
35

36
    /** Multibase prefix character for Base58-BTC encoding. */
37
    private static final char MULTIBASE_BASE58BTC_PREFIX = 'z';
38

39
    /** Curve name constant for P-256 (secp256r1). */
40
    private static final String CURVE_P256 = "secp256r1";
41

42
    /** Curve name constant for P-384 (secp384r1). */
43
    private static final String CURVE_P384 = "secp384r1";
44

45
    /** Multicodec unsigned varint prefix for P-256 (secp256r1) public keys (code 0x1200). */
46
    static final byte[] MULTICODEC_P256_PREFIX = {(byte) 0x80, (byte) 0x24};
1✔
47

48
    /** Multicodec unsigned varint prefix for P-384 (secp384r1) public keys (code 0x1201). */
49
    static final byte[] MULTICODEC_P384_PREFIX = {(byte) 0x81, (byte) 0x24};
1✔
50

51
    /** Multicodec unsigned varint prefix for Ed25519 public keys (code 0xED). */
52
    static final byte[] MULTICODEC_ED25519_PREFIX = {(byte) 0xED, (byte) 0x01};
1✔
53

54
    /** The order of the P-256 curve, used for curve identification. */
55
    private static final BigInteger P256_ORDER =
1✔
56
            ECNamedCurveTable.getParameterSpec(CURVE_P256).getN();
1✔
57

58
    /** The order of the P-384 curve, used for curve identification. */
59
    private static final BigInteger P384_ORDER =
1✔
60
            ECNamedCurveTable.getParameterSpec(CURVE_P384).getN();
1✔
61

62
    /** Error message template for unsupported key types. */
63
    private static final String UNSUPPORTED_KEY_TYPE_MESSAGE =
64
            "Unsupported key type: %s. Supported types: EC (P-256, P-384).";
65

66
    /** Error message for unsupported EC curves. */
67
    private static final String UNSUPPORTED_CURVE_MESSAGE =
68
            "Unsupported EC curve. Supported curves: secp256r1 (P-256), secp384r1 (P-384).";
69

70
    private DidKeyGenerator() {
71
        // Utility class - not instantiable
72
    }
73

74
    /**
75
     * Generates a {@code did:key} URI from the given private key.
76
     *
77
     * <p>The method derives the public key from the private key, applies the
78
     * appropriate multicodec prefix, Base58-BTC encodes the result with the
79
     * multibase {@code z} prefix, and returns the full {@code did:key} URI.</p>
80
     *
81
     * @param privateKey the private key (EC P-256 or P-384)
82
     * @return the generated {@code did:key} URI
83
     * @throws IllegalArgumentException if the key type or curve is not supported
84
     */
85
    public static URI generateDidKey(PrivateKey privateKey) {
86
        if (privateKey instanceof ECPrivateKey ecPrivateKey) {
1✔
87
            return generateEcDidKey(ecPrivateKey);
1✔
88
        }
89
        throw new IllegalArgumentException(
1✔
90
                String.format(UNSUPPORTED_KEY_TYPE_MESSAGE, privateKey.getAlgorithm()));
1✔
91
    }
92

93
    /**
94
     * Generates a {@code did:key} URI from an EC private key by deriving the
95
     * compressed public key point, prepending the multicodec prefix, and encoding.
96
     */
97
    private static URI generateEcDidKey(ECPrivateKey ecPrivateKey) {
98
        String curveName = identifyCurve(ecPrivateKey);
1✔
99
        byte[] multicodecPrefix = getMulticodecPrefix(curveName);
1✔
100
        byte[] compressedPublicKey = deriveCompressedPublicKey(ecPrivateKey, curveName);
1✔
101

102
        byte[] prefixedKey = new byte[multicodecPrefix.length + compressedPublicKey.length];
1✔
103
        System.arraycopy(multicodecPrefix, 0, prefixedKey, 0, multicodecPrefix.length);
1✔
104
        System.arraycopy(compressedPublicKey, 0, prefixedKey, multicodecPrefix.length, compressedPublicKey.length);
1✔
105

106
        String encoded = Base58.encode(prefixedKey);
1✔
107
        return URI.create(DID_KEY_PREFIX + MULTIBASE_BASE58BTC_PREFIX + encoded);
1✔
108
    }
109

110
    /**
111
     * Identifies the EC curve by comparing the key's curve order against known NIST curves.
112
     *
113
     * @param ecPrivateKey the EC private key to identify
114
     * @return the standard curve name (e.g. "secp256r1")
115
     * @throws IllegalArgumentException if the curve is not supported
116
     */
117
    private static String identifyCurve(ECPrivateKey ecPrivateKey) {
118
        BigInteger order = ecPrivateKey.getParams().getOrder();
1✔
119
        if (P256_ORDER.equals(order)) {
1✔
120
            return CURVE_P256;
1✔
121
        } else if (P384_ORDER.equals(order)) {
1✔
122
            return CURVE_P384;
1✔
123
        }
NEW
124
        throw new IllegalArgumentException(UNSUPPORTED_CURVE_MESSAGE);
×
125
    }
126

127
    /**
128
     * Returns the multicodec unsigned varint prefix bytes for the given curve name.
129
     */
130
    private static byte[] getMulticodecPrefix(String curveName) {
131
        return switch (curveName) {
1✔
132
            case CURVE_P256 -> MULTICODEC_P256_PREFIX;
1✔
133
            case CURVE_P384 -> MULTICODEC_P384_PREFIX;
1✔
NEW
134
            default -> throw new IllegalArgumentException(UNSUPPORTED_CURVE_MESSAGE);
×
135
        };
136
    }
137

138
    /**
139
     * Derives the compressed public key bytes from an EC private key.
140
     *
141
     * <p>Uses the curve's generator point {@code G} and the private scalar {@code d}
142
     * to compute the public point {@code Q = d * G}, then returns its compressed encoding.</p>
143
     */
144
    private static byte[] deriveCompressedPublicKey(ECPrivateKey ecPrivateKey, String curveName) {
145
        ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(curveName);
1✔
146
        BigInteger d = ecPrivateKey.getS();
1✔
147
        ECPoint publicPoint = spec.getG().multiply(d).normalize();
1✔
148
        return publicPoint.getEncoded(true);
1✔
149
    }
150

151
    /**
152
     * Base58-BTC encoder using the Bitcoin alphabet.
153
     *
154
     * <p>Implements the standard Base58 encoding as used by Bitcoin and the
155
     * <a href="https://github.com/multiformats/multibase">multibase</a> specification
156
     * for the {@code z} (base58btc) prefix. The alphabet excludes characters
157
     * {@code 0}, {@code O}, {@code I}, and {@code l} to avoid visual ambiguity.</p>
158
     */
159
    static final class Base58 {
160

161
        /** The Base58 Bitcoin alphabet. */
162
        private static final String ALPHABET =
163
                "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
164

165
        /** Base value for the encoding. */
166
        private static final BigInteger BASE = BigInteger.valueOf(58);
1✔
167

168
        private Base58() {
169
            // Utility class - not instantiable
170
        }
171

172
        /**
173
         * Encodes the given byte array using Base58-BTC encoding.
174
         *
175
         * <p>Leading zero bytes in the input are preserved as leading {@code '1'}
176
         * characters in the output, matching the Bitcoin Base58Check convention.</p>
177
         *
178
         * @param input the bytes to encode (must not be null)
179
         * @return the Base58-BTC encoded string, or an empty string for empty input
180
         */
181
        static String encode(byte[] input) {
182
            if (input.length == 0) {
1✔
183
                return "";
1✔
184
            }
185

186
            // Count leading zero bytes - each maps to a '1' in the output
187
            int leadingZeros = 0;
1✔
188
            while (leadingZeros < input.length && input[leadingZeros] == 0) {
1✔
189
                leadingZeros++;
1✔
190
            }
191

192
            // Convert the byte array to a BigInteger and repeatedly divide by 58
193
            BigInteger value = new BigInteger(1, input);
1✔
194
            StringBuilder sb = new StringBuilder();
1✔
195
            while (value.compareTo(BigInteger.ZERO) > 0) {
1✔
196
                BigInteger[] divmod = value.divideAndRemainder(BASE);
1✔
197
                value = divmod[0];
1✔
198
                sb.append(ALPHABET.charAt(divmod[1].intValue()));
1✔
199
            }
1✔
200

201
            // Prepend '1' characters for each leading zero byte
202
            for (int i = 0; i < leadingZeros; i++) {
1✔
203
                sb.append('1');
1✔
204
            }
205

206
            return sb.reverse().toString();
1✔
207
        }
208
    }
209
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc