001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.jose.jwk; 019 020 021import java.net.URI; 022import java.security.KeyPair; 023import java.security.KeyStore; 024import java.security.PrivateKey; 025import java.security.PublicKey; 026import java.security.cert.X509Certificate; 027import java.text.ParseException; 028import java.util.*; 029 030import net.jcip.annotations.Immutable; 031import net.minidev.json.JSONObject; 032 033import com.nimbusds.jose.Algorithm; 034import com.nimbusds.jose.JOSEException; 035import com.nimbusds.jose.util.Base64; 036import com.nimbusds.jose.util.Base64URL; 037import com.nimbusds.jose.util.ByteUtils; 038import com.nimbusds.jose.util.JSONObjectUtils; 039 040 041/** 042 * {@link KeyType#OKP Octet key pair} JSON Web Key (JWK), used to represent 043 * Edwards-curve keys. This class is immutable. 044 * 045 * <p>Supported curves: 046 * 047 * <ul> 048 * <li>{@link Curve#Ed25519 Ed25519} 049 * <li>{@link Curve#Ed448 Ed448} 050 * <li>{@link Curve#X25519 X25519} 051 * <li>{@link Curve#X448 X448} 052 * </ul> 053 * 054 * <p>Example JSON object representation of a public OKP JWK: 055 * 056 * <pre> 057 * { 058 * "kty" : "OKP", 059 * "crv" : "Ed25519", 060 * "x" : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", 061 * "use" : "sig", 062 * "kid" : "1" 063 * } 064 * </pre> 065 * 066 * <p>Example JSON object representation of a private OKP JWK: 067 * 068 * <pre> 069 * { 070 * "kty" : "OKP", 071 * "crv" : "Ed25519", 072 * "x" : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", 073 * "d" : "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", 074 * "use" : "sig", 075 * "kid" : "1" 076 * } 077 * </pre> 078 * 079 * <p>Use the builder to create a new OKP JWK: 080 * 081 * <pre> 082 * OctetKeyPair key = new OctetKeyPair.Builder(Curve.Ed25519, x) 083 * .keyUse(KeyUse.SIGNATURE) 084 * .keyID("1") 085 * .build(); 086 * </pre> 087 * 088 * @author Vladimir Dzhuvinov 089 * @version 2019-04-15 090 */ 091@Immutable 092public class OctetKeyPair extends JWK implements AsymmetricJWK, CurveBasedJWK { 093 094 095 private static final long serialVersionUID = 1L; 096 097 098 /** 099 * Supported Edwards curves. 100 */ 101 public static final Set<Curve> SUPPORTED_CURVES = Collections.unmodifiableSet( 102 new HashSet<>(Arrays.asList(Curve.Ed25519, Curve.Ed448, Curve.X25519, Curve.X448)) 103 ); 104 105 106 /** 107 * Builder for constructing Octet Key Pair JWKs. 108 * 109 * <p>Example usage: 110 * 111 * <pre> 112 * OctetKeyPair key = new OctetKeyPair.Builder(Curve.Ed25519, x) 113 * .d(d) 114 * .algorithm(JWSAlgorithm.EdDSA) 115 * .keyID("1") 116 * .build(); 117 * </pre> 118 */ 119 public static class Builder { 120 121 122 /** 123 * The curve name. 124 */ 125 private final Curve crv; 126 127 128 /** 129 * The public 'x' parameter. 130 */ 131 private final Base64URL x; 132 133 134 /** 135 * The private 'd' parameter, optional. 136 */ 137 private Base64URL d; 138 139 140 /** 141 * The key use, optional. 142 */ 143 private KeyUse use; 144 145 146 /** 147 * The key operations, optional. 148 */ 149 private Set<KeyOperation> ops; 150 151 152 /** 153 * The intended JOSE algorithm for the key, optional. 154 */ 155 private Algorithm alg; 156 157 158 /** 159 * The key ID, optional. 160 */ 161 private String kid; 162 163 164 /** 165 * X.509 certificate URL, optional. 166 */ 167 private URI x5u; 168 169 170 /** 171 * X.509 certificate SHA-1 thumbprint, optional. 172 */ 173 @Deprecated 174 private Base64URL x5t; 175 176 177 /** 178 * X.509 certificate SHA-256 thumbprint, optional. 179 */ 180 private Base64URL x5t256; 181 182 183 /** 184 * The X.509 certificate chain, optional. 185 */ 186 private List<Base64> x5c; 187 188 189 /** 190 * Reference to the underlying key store, {@code null} if none. 191 */ 192 private KeyStore ks; 193 194 195 /** 196 * Creates a new Octet Key Pair JWK builder. 197 * 198 * @param crv The cryptographic curve. Must not be 199 * {@code null}. 200 * @param x The public 'x' parameter. Must not be 201 * {@code null}. 202 */ 203 public Builder(final Curve crv, final Base64URL x) { 204 205 if (crv == null) { 206 throw new IllegalArgumentException("The curve must not be null"); 207 } 208 209 this.crv = crv; 210 211 if (x == null) { 212 throw new IllegalArgumentException("The 'x' coordinate must not be null"); 213 } 214 215 this.x = x; 216 } 217 218 219 /** 220 * Creates a new Octet Key Pair JWK builder. 221 * 222 * @param okpJWK The Octet Key Pair to start with. Must not be 223 * {@code null}. 224 */ 225 public Builder(final OctetKeyPair okpJWK) { 226 227 crv = okpJWK.crv; 228 x = okpJWK.x; 229 d = okpJWK.d; 230 use = okpJWK.getKeyUse(); 231 ops = okpJWK.getKeyOperations(); 232 alg = okpJWK.getAlgorithm(); 233 kid = okpJWK.getKeyID(); 234 x5u = okpJWK.getX509CertURL(); 235 x5t = okpJWK.getX509CertThumbprint(); 236 x5t256 = okpJWK.getX509CertSHA256Thumbprint(); 237 x5c = okpJWK.getX509CertChain(); 238 ks = okpJWK.getKeyStore(); 239 } 240 241 242 /** 243 * Sets the private 'd' parameter. 244 * 245 * @param d The private 'd' parameter, {@code null} if not 246 * specified (for a public key). 247 * 248 * @return This builder. 249 */ 250 public OctetKeyPair.Builder d(final Base64URL d) { 251 252 this.d = d; 253 return this; 254 } 255 256 257 /** 258 * Sets the use ({@code use}) of the JWK. 259 * 260 * @param use The key use, {@code null} if not specified or if 261 * the key is intended for signing as well as 262 * encryption. 263 * 264 * @return This builder. 265 */ 266 public OctetKeyPair.Builder keyUse(final KeyUse use) { 267 268 this.use = use; 269 return this; 270 } 271 272 273 /** 274 * Sets the operations ({@code key_ops}) of the JWK. 275 * 276 * @param ops The key operations, {@code null} if not 277 * specified. 278 * 279 * @return This builder. 280 */ 281 public OctetKeyPair.Builder keyOperations(final Set<KeyOperation> ops) { 282 283 this.ops = ops; 284 return this; 285 } 286 287 288 /** 289 * Sets the intended JOSE algorithm ({@code alg}) for the JWK. 290 * 291 * @param alg The intended JOSE algorithm, {@code null} if not 292 * specified. 293 * 294 * @return This builder. 295 */ 296 public OctetKeyPair.Builder algorithm(final Algorithm alg) { 297 298 this.alg = alg; 299 return this; 300 } 301 302 /** 303 * Sets the ID ({@code kid}) of the JWK. The key ID can be used 304 * to match a specific key. This can be used, for instance, to 305 * choose a key within a {@link JWKSet} during key rollover. 306 * The key ID may also correspond to a JWS/JWE {@code kid} 307 * header parameter value. 308 * 309 * @param kid The key ID, {@code null} if not specified. 310 * 311 * @return This builder. 312 */ 313 public OctetKeyPair.Builder keyID(final String kid) { 314 315 this.kid = kid; 316 return this; 317 } 318 319 320 /** 321 * Sets the ID ({@code kid}) of the JWK to its SHA-256 JWK 322 * thumbprint (RFC 7638). The key ID can be used to match a 323 * specific key. This can be used, for instance, to choose a 324 * key within a {@link JWKSet} during key rollover. The key ID 325 * may also correspond to a JWS/JWE {@code kid} header 326 * parameter value. 327 * 328 * @return This builder. 329 * 330 * @throws JOSEException If the SHA-256 hash algorithm is not 331 * supported. 332 */ 333 public OctetKeyPair.Builder keyIDFromThumbprint() 334 throws JOSEException { 335 336 return keyIDFromThumbprint("SHA-256"); 337 } 338 339 340 /** 341 * Sets the ID ({@code kid}) of the JWK to its JWK thumbprint 342 * (RFC 7638). The key ID can be used to match a specific key. 343 * This can be used, for instance, to choose a key within a 344 * {@link JWKSet} during key rollover. The key ID may also 345 * correspond to a JWS/JWE {@code kid} header parameter value. 346 * 347 * @param hashAlg The hash algorithm for the JWK thumbprint 348 * computation. Must not be {@code null}. 349 * 350 * @return This builder. 351 * 352 * @throws JOSEException If the hash algorithm is not 353 * supported. 354 */ 355 public OctetKeyPair.Builder keyIDFromThumbprint(final String hashAlg) 356 throws JOSEException { 357 358 // Put mandatory params in sorted order 359 LinkedHashMap<String,String> requiredParams = new LinkedHashMap<>(); 360 requiredParams.put("crv", crv.toString()); 361 requiredParams.put("kty", KeyType.OKP.getValue()); 362 requiredParams.put("x", x.toString()); 363 this.kid = ThumbprintUtils.compute(hashAlg, requiredParams).toString(); 364 return this; 365 } 366 367 368 /** 369 * Sets the X.509 certificate URL ({@code x5u}) of the JWK. 370 * 371 * @param x5u The X.509 certificate URL, {@code null} if not 372 * specified. 373 * 374 * @return This builder. 375 */ 376 public OctetKeyPair.Builder x509CertURL(final URI x5u) { 377 378 this.x5u = x5u; 379 return this; 380 } 381 382 383 /** 384 * Sets the X.509 certificate SHA-1 thumbprint ({@code x5t}) of 385 * the JWK. 386 * 387 * @param x5t The X.509 certificate SHA-1 thumbprint, 388 * {@code null} if not specified. 389 * 390 * @return This builder. 391 */ 392 @Deprecated 393 public OctetKeyPair.Builder x509CertThumbprint(final Base64URL x5t) { 394 395 this.x5t = x5t; 396 return this; 397 } 398 399 400 /** 401 * Sets the X.509 certificate SHA-256 thumbprint 402 * ({@code x5t#S256}) of the JWK. 403 * 404 * @param x5t256 The X.509 certificate SHA-256 thumbprint, 405 * {@code null} if not specified. 406 * 407 * @return This builder. 408 */ 409 public OctetKeyPair.Builder x509CertSHA256Thumbprint(final Base64URL x5t256) { 410 411 this.x5t256 = x5t256; 412 return this; 413 } 414 415 416 /** 417 * Sets the X.509 certificate chain ({@code x5c}) of the JWK. 418 * 419 * @param x5c The X.509 certificate chain as a unmodifiable 420 * list, {@code null} if not specified. 421 * 422 * @return This builder. 423 */ 424 public OctetKeyPair.Builder x509CertChain(final List<Base64> x5c) { 425 426 this.x5c = x5c; 427 return this; 428 } 429 430 431 /** 432 * Sets the underlying key store. 433 * 434 * @param keyStore Reference to the underlying key store, 435 * {@code null} if none. 436 * 437 * @return This builder. 438 */ 439 public OctetKeyPair.Builder keyStore(final KeyStore keyStore) { 440 441 this.ks = keyStore; 442 return this; 443 } 444 445 446 /** 447 * Builds a new Octet Key Pair JWK. 448 * 449 * @return The Octet Key Pair JWK. 450 * 451 * @throws IllegalStateException If the JWK parameters were 452 * inconsistently specified. 453 */ 454 public OctetKeyPair build() { 455 456 try { 457 if (d == null) { 458 // Public key 459 return new OctetKeyPair(crv, x, use, ops, alg, kid, x5u, x5t, x5t256, x5c, ks); 460 } 461 462 // Public / private key pair with 'd' 463 return new OctetKeyPair(crv, x, d, use, ops, alg, kid, x5u, x5t, x5t256, x5c, ks); 464 465 } catch (IllegalArgumentException e) { 466 throw new IllegalStateException(e.getMessage(), e); 467 } 468 } 469 } 470 471 472 /** 473 * The curve name. 474 */ 475 private final Curve crv; 476 477 478 /** 479 * The public 'x' parameter. 480 */ 481 private final Base64URL x; 482 483 484 /** 485 * The public 'x' parameter, decoded from Base64. 486 * Cached for performance and to reduce the risk of side channel attacks 487 * against the Base64 decoding procedure. 488 */ 489 private final byte[] decodedX; 490 491 492 /** 493 * The private 'd' parameter. 494 */ 495 private final Base64URL d; 496 497 498 /** 499 * The private 'd' parameter, decoded from Base64. 500 * Cached for performance and to reduce the risk of side channel attacks 501 * against the Base64 decoding procedure. 502 */ 503 private final byte[] decodedD; 504 505 506 /** 507 * Creates a new public Octet Key Pair JSON Web Key (JWK) with the 508 * specified parameters. 509 * 510 * @param crv The cryptographic curve. Must not be {@code null}. 511 * @param x The public 'x' parameter. Must not be {@code null}. 512 * @param use The key use, {@code null} if not specified or if the 513 * key is intended for signing as well as encryption. 514 * @param ops The key operations, {@code null} if not specified. 515 * @param alg The intended JOSE algorithm for the key, {@code null} 516 * if not specified. 517 * @param kid The key ID, {@code null} if not specified. 518 * @param x5u The X.509 certificate URL, {@code null} if not 519 * specified. 520 * @param x5t The X.509 certificate SHA-1 thumbprint, {@code null} 521 * if not specified. 522 * @param x5t256 The X.509 certificate SHA-256 thumbprint, {@code null} 523 * if not specified. 524 * @param x5c The X.509 certificate chain, {@code null} if not 525 * specified. 526 * @param ks Reference to the underlying key store, {@code null} if 527 * not specified. 528 */ 529 public OctetKeyPair(final Curve crv, final Base64URL x, 530 final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, 531 final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, 532 final KeyStore ks) { 533 534 super(KeyType.OKP, use, ops, alg, kid, x5u, x5t, x5t256, x5c, ks); 535 536 if (crv == null) { 537 throw new IllegalArgumentException("The curve must not be null"); 538 } 539 540 if (! SUPPORTED_CURVES.contains(crv)) { 541 throw new IllegalArgumentException("Unknown / unsupported curve: " + crv); 542 } 543 544 this.crv = crv; 545 546 if (x == null) { 547 throw new IllegalArgumentException("The 'x' parameter must not be null"); 548 } 549 550 this.x = x; 551 decodedX = x.decode(); 552 553 d = null; 554 decodedD = null; 555 } 556 557 558 /** 559 * Creates a new public / private Octet Key Pair JSON Web Key (JWK) 560 * with the specified parameters. 561 * 562 * @param crv The cryptographic curve. Must not be {@code null}. 563 * @param x The public 'x' parameter. Must not be {@code null}. 564 * @param d The private 'd' parameter. Must not be {@code null}. 565 * @param use The key use, {@code null} if not specified or if the 566 * key is intended for signing as well as encryption. 567 * @param ops The key operations, {@code null} if not specified. 568 * @param alg The intended JOSE algorithm for the key, {@code null} 569 * if not specified. 570 * @param kid The key ID, {@code null} if not specified. 571 * @param x5u The X.509 certificate URL, {@code null} if not 572 * specified. 573 * @param x5t The X.509 certificate SHA-1 thumbprint, {@code null} 574 * if not specified. 575 * @param x5t256 The X.509 certificate SHA-256 thumbprint, {@code null} 576 * if not specified. 577 * @param x5c The X.509 certificate chain, {@code null} if not 578 * specified. 579 * @param ks Reference to the underlying key store, {@code null} if 580 * not specified. 581 */ 582 public OctetKeyPair(final Curve crv, final Base64URL x, final Base64URL d, 583 final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, 584 final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, 585 final KeyStore ks) { 586 587 super(KeyType.OKP, use, ops, alg, kid, x5u, x5t, x5t256, x5c, ks); 588 589 if (crv == null) { 590 throw new IllegalArgumentException("The curve must not be null"); 591 } 592 593 if (! SUPPORTED_CURVES.contains(crv)) { 594 throw new IllegalArgumentException("Unknown / unsupported curve: " + crv); 595 } 596 597 this.crv = crv; 598 599 if (x == null) { 600 throw new IllegalArgumentException("The 'x' parameter must not be null"); 601 } 602 603 this.x = x; 604 decodedX = x.decode(); 605 606 if (d == null) { 607 throw new IllegalArgumentException("The 'd' parameter must not be null"); 608 } 609 610 this.d = d; 611 decodedD = d.decode(); 612 } 613 614 615 @Override 616 public Curve getCurve() { 617 618 return crv; 619 } 620 621 622 /** 623 * Gets the public 'x' parameter. 624 * 625 * @return The public 'x' parameter. 626 */ 627 public Base64URL getX() { 628 629 return x; 630 } 631 632 633 /** 634 * Gets the public 'x' parameter, decoded from Base64. 635 * 636 * @return The public 'x' parameter in bytes. 637 */ 638 public byte[] getDecodedX() { 639 640 return decodedX.clone(); 641 } 642 643 644 /** 645 * Gets the private 'd' parameter. 646 * 647 * @return The private 'd' coordinate, {@code null} if not specified 648 * (for a public key). 649 */ 650 public Base64URL getD() { 651 652 return d; 653 } 654 655 656 /** 657 * Gets the private 'd' parameter, decoded from Base64. 658 * 659 * @return The private 'd' coordinate in bytes, {@code null} if not specified 660 * (for a public key). 661 */ 662 public byte[] getDecodedD() { 663 664 return decodedD == null ? null : decodedD.clone(); 665 } 666 667 668 @Override 669 public PublicKey toPublicKey() 670 throws JOSEException { 671 672 throw new JOSEException("Export to java.security.PublicKey not supported"); 673 } 674 675 676 @Override 677 public PrivateKey toPrivateKey() 678 throws JOSEException { 679 680 throw new JOSEException("Export to java.security.PrivateKey not supported"); 681 } 682 683 684 @Override 685 public KeyPair toKeyPair() 686 throws JOSEException { 687 688 throw new JOSEException("Export to java.security.KeyPair not supported"); 689 } 690 691 692 @Override 693 public boolean matches(final X509Certificate cert) { 694 // X.509 certs don't support OKP yet 695 return false; 696 } 697 698 699 @Override 700 public LinkedHashMap<String,?> getRequiredParams() { 701 702 // Put mandatory params in sorted order 703 LinkedHashMap<String,String> requiredParams = new LinkedHashMap<>(); 704 requiredParams.put("crv", crv.toString()); 705 requiredParams.put("kty", getKeyType().getValue()); 706 requiredParams.put("x", x.toString()); 707 return requiredParams; 708 } 709 710 711 @Override 712 public boolean isPrivate() { 713 714 return d != null; 715 } 716 717 718 /** 719 * Returns a copy of this Octet Key Pair JWK with any private values 720 * removed. 721 * 722 * @return The copied public Octet Key Pair JWK. 723 */ 724 @Override 725 public OctetKeyPair toPublicJWK() { 726 727 return new OctetKeyPair( 728 getCurve(), getX(), 729 getKeyUse(), getKeyOperations(), getAlgorithm(), getKeyID(), 730 getX509CertURL(), getX509CertThumbprint(), getX509CertSHA256Thumbprint(), getX509CertChain(), 731 getKeyStore()); 732 } 733 734 735 @Override 736 public JSONObject toJSONObject() { 737 738 JSONObject o = super.toJSONObject(); 739 740 // Append OKP specific attributes 741 o.put("crv", crv.toString()); 742 o.put("x", x.toString()); 743 744 if (d != null) { 745 o.put("d", d.toString()); 746 } 747 748 return o; 749 } 750 751 752 @Override 753 public int size() { 754 755 return ByteUtils.bitLength(x.decode()); 756 } 757 758 759 /** 760 * Parses a public / private Octet Key Pair JWK from the specified JSON 761 * object string representation. 762 * 763 * @param s The JSON object string to parse. Must not be {@code null}. 764 * 765 * @return The public / private Octet Key Pair JWK. 766 * 767 * @throws ParseException If the string couldn't be parsed to an Octet 768 * Key Pair JWK. 769 */ 770 public static OctetKeyPair parse(final String s) 771 throws ParseException { 772 773 return parse(JSONObjectUtils.parse(s)); 774 } 775 776 777 /** 778 * Parses a public / private Octet Key Pair JWK from the specified JSON 779 * object representation. 780 * 781 * @param jsonObject The JSON object to parse. Must not be 782 * {@code null}. 783 * 784 * @return The public / private Octet Key Pair JWK. 785 * 786 * @throws ParseException If the JSON object couldn't be parsed to an 787 * Octet Key Pair JWK. 788 */ 789 public static OctetKeyPair parse(final JSONObject jsonObject) 790 throws ParseException { 791 792 // Parse the mandatory parameters first 793 Curve crv = Curve.parse(JSONObjectUtils.getString(jsonObject, "crv")); 794 Base64URL x = new Base64URL(JSONObjectUtils.getString(jsonObject, "x")); 795 796 // Check key type 797 KeyType kty = JWKMetadata.parseKeyType(jsonObject); 798 799 if (kty != KeyType.OKP) { 800 throw new ParseException("The key type \"kty\" must be OKP", 0); 801 } 802 803 // Get optional private key 804 Base64URL d = null; 805 if (jsonObject.get("d") != null) { 806 d = new Base64URL(JSONObjectUtils.getString(jsonObject, "d")); 807 } 808 809 810 try { 811 if (d == null) { 812 // Public key 813 return new OctetKeyPair(crv, x, 814 JWKMetadata.parseKeyUse(jsonObject), 815 JWKMetadata.parseKeyOperations(jsonObject), 816 JWKMetadata.parseAlgorithm(jsonObject), 817 JWKMetadata.parseKeyID(jsonObject), 818 JWKMetadata.parseX509CertURL(jsonObject), 819 JWKMetadata.parseX509CertThumbprint(jsonObject), 820 JWKMetadata.parseX509CertSHA256Thumbprint(jsonObject), 821 JWKMetadata.parseX509CertChain(jsonObject), 822 null); 823 824 } else { 825 // Key pair 826 return new OctetKeyPair(crv, x, d, 827 JWKMetadata.parseKeyUse(jsonObject), 828 JWKMetadata.parseKeyOperations(jsonObject), 829 JWKMetadata.parseAlgorithm(jsonObject), 830 JWKMetadata.parseKeyID(jsonObject), 831 JWKMetadata.parseX509CertURL(jsonObject), 832 JWKMetadata.parseX509CertThumbprint(jsonObject), 833 JWKMetadata.parseX509CertSHA256Thumbprint(jsonObject), 834 JWKMetadata.parseX509CertChain(jsonObject), 835 null); 836 } 837 838 } catch (IllegalArgumentException ex) { 839 840 // Conflicting 'use' and 'key_ops' 841 throw new ParseException(ex.getMessage(), 0); 842 } 843 } 844 845 846 @Override 847 public boolean equals(Object o) { 848 if (this == o) return true; 849 if (!(o instanceof OctetKeyPair)) return false; 850 if (!super.equals(o)) return false; 851 OctetKeyPair that = (OctetKeyPair) o; 852 return Objects.equals(crv, that.crv) && 853 Objects.equals(x, that.x) && 854 Arrays.equals(decodedX, that.decodedX) && 855 Objects.equals(d, that.d) && 856 Arrays.equals(decodedD, that.decodedD); 857 } 858 859 860 @Override 861 public int hashCode() { 862 int result = Objects.hash(super.hashCode(), crv, x, d); 863 result = 31 * result + Arrays.hashCode(decodedX); 864 result = 31 * result + Arrays.hashCode(decodedD); 865 return result; 866 } 867}