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