All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xrpl.xrpl4j.keypairs.Secp256k1KeyPairService Maven / Gradle / Ivy

package org.xrpl.xrpl4j.keypairs;

/*-
 * ========================LICENSE_START=================================
 * xrpl4j :: keypairs
 * %%
 * Copyright (C) 2020 - 2022 XRPL Foundation and its contributors
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * =========================LICENSE_END==================================
 */

import static org.xrpl.xrpl4j.keypairs.Secp256k1.ecDomainParameters;

import com.google.common.io.BaseEncoding;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.crypto.signers.HMacDSAKCalculator;
import org.bouncycastle.math.ec.ECPoint;
import org.xrpl.xrpl4j.codec.addresses.UnsignedByteArray;
import org.xrpl.xrpl4j.codec.addresses.VersionType;

import java.math.BigInteger;
import java.util.Optional;

/**
 * Implementation of {@link KeyPairService} which uses the ECDSA algorithm with the secp256k1 curve to derive keys
 * and sign/verify signatures.
 */
public class Secp256k1KeyPairService extends AbstractKeyPairService {

  private static final Secp256k1KeyPairService INSTANCE = new Secp256k1KeyPairService();

  public static Secp256k1KeyPairService getInstance() {
    return INSTANCE;
  }

  @Override
  public String generateSeed(UnsignedByteArray entropy) {
    return addressCodec.encodeSeed(entropy, VersionType.SECP256K1);
  }


  @Override
  public KeyPair deriveKeyPair(String seed) {
    return deriveKeyPair(addressCodec.decodeSeed(seed).bytes(), 0);
  }

  /**
   * Note that multiple keypairs can be derived from the same seed using the secp2551k algorithm by
   * deriving the keys from a seed and an account index UInt32. However, this use case is incredibly uncommon,
   * and a vast majority of users use 0 for the account index. Thus, this implementation does not allow for custom
   * account indexes for deriving secp2551k keys.
   *
   * @param seed An {@link UnsignedByteArray} of length 16 containing a seed.
   *
   * @return A {@link KeyPair} containing a public/private keypair derived from seed using the secp2561k algorithm.
   */
  private KeyPair deriveKeyPair(UnsignedByteArray seed) {
    int accountNumber = 0;
    return deriveKeyPair(seed, accountNumber);
  }

  private KeyPair deriveKeyPair(UnsignedByteArray seed, int accountNumber) {
    // private key needs to be a BigInteger so we can derive the public key by multiplying G by the private key.
    BigInteger privateKey = derivePrivateKey(seed, accountNumber);
    UnsignedByteArray publicKey = derivePublicKey(privateKey);

    return KeyPair.builder()
      .privateKey(UnsignedByteArray.of(privateKey.toByteArray()).hexValue())
      .publicKey(UnsignedByteArray.of(publicKey.toByteArray()).hexValue())
      .build();
  }

  private UnsignedByteArray derivePublicKey(BigInteger privateKey) {
    return UnsignedByteArray.of(ecDomainParameters.getG().multiply(privateKey).getEncoded(true));
  }

  private BigInteger derivePrivateKey(UnsignedByteArray seed, int accountNumber) {
    BigInteger privateGen = deriveScalar(seed);
    if (accountNumber == -1) {
      return privateGen;
    }

    UnsignedByteArray publicGen = UnsignedByteArray.of(ecDomainParameters.getG().multiply(privateGen).getEncoded(true));
    return deriveScalar(publicGen, accountNumber)
      .add(privateGen)
      .mod(ecDomainParameters.getN());
  }

  private BigInteger deriveScalar(UnsignedByteArray seed) {
    return deriveScalar(seed, Optional.empty());
  }

  private BigInteger deriveScalar(UnsignedByteArray seed, Integer discriminator) {
    return deriveScalar(seed, Optional.of(discriminator));
  }

  @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
  private BigInteger deriveScalar(UnsignedByteArray seed, Optional discriminator) {
    BigInteger key = null;
    UnsignedByteArray seedCopy = UnsignedByteArray.of(seed.toByteArray());
    for (long i = 0; i <= 0xFFFFFFFFL; i++) {
      discriminator.map(d -> HashUtils.addUInt32(seedCopy, d));
      HashUtils.addUInt32(seedCopy, (int) i);
      UnsignedByteArray hash = HashUtils.sha512Half(seedCopy);
      key = new BigInteger(1, hash.toByteArray());
      if (key.compareTo(BigInteger.ZERO) > 0 && key.compareTo(ecDomainParameters.getN()) < 0) {
        break;
      }
    }

    return key;
  }

  @Override
  public String sign(UnsignedByteArray message, String privateKey) {
    UnsignedByteArray messageHash = HashUtils.sha512Half(message);
    EcDsaSignature signature = createEcdsaSignature(messageHash, new BigInteger(privateKey, 16));
    return signature.der().hexValue();
  }

  @SuppressWarnings("LocalVariableName")
  private EcDsaSignature createEcdsaSignature(UnsignedByteArray messageHash, BigInteger privateKey) {
    ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
    ECPrivateKeyParameters parameters = new ECPrivateKeyParameters(privateKey, ecDomainParameters);
    signer.init(true, parameters);
    BigInteger[] signatures = signer.generateSignature(messageHash.toByteArray());
    BigInteger r = signatures[0];
    BigInteger s = signatures[1];
    BigInteger otherS = ecDomainParameters.getN().subtract(s);
    if (s.compareTo(otherS) > 0) {
      s = otherS;
    }

    return EcDsaSignature.builder()
      .r(r)
      .s(s)
      .build();
  }

  @Override
  public boolean verify(UnsignedByteArray message, String signature, String publicKey) {
    UnsignedByteArray messageHash = HashUtils.sha512Half(message);
    EcDsaSignature sig = EcDsaSignature.fromDer(BaseEncoding.base16().decode(signature));
    if (sig == null) {
      return false;
    }

    ECDSASigner signer = new ECDSASigner();
    ECPoint publicKeyPoint = ecDomainParameters.getCurve().decodePoint(BaseEncoding.base16().decode(publicKey));
    ECPublicKeyParameters params = new ECPublicKeyParameters(publicKeyPoint, ecDomainParameters);
    signer.init(false, params);
    return signer.verifySignature(messageHash.toByteArray(), sig.r(), sig.s());
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy