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

org.beykery.util.bip44.SignedMessage Maven / Gradle / Ivy

There is a newer version: 1.0.5
Show newest version
/*
 * Copyright 2013, 2014 Megion Research & Development GmbH
 *
 * 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.
 */

package org.beykery.util.bip44;

import com.google.common.base.Preconditions;
import org.beykery.util.bip44.ec.Curve;
import org.beykery.util.bip44.ec.EcTools;
import org.beykery.util.bip44.ec.Parameters;
import org.beykery.util.bip44.ec.Point;

import java.io.Serializable;
import java.math.BigInteger;

public class SignedMessage implements Serializable {
   private static final long serialVersionUID = 1188125594280603453L;

   private final Signature signature;
   private final PublicKey publicKey;
   private final int recId;

   private SignedMessage(Signature signature, PublicKey publicKey, int recId) {
      this.signature = signature;
      this.publicKey = publicKey;
      this.recId = recId;
   }

   public static PublicKey recoverFromSignature(String message, String signatureBase64) throws WrongSignatureException {
      final byte[] signatureEncoded = Base64.decode(signatureBase64);
      final Signature sig = decodeSignature(signatureEncoded);
      return recoverFromSignature(message, signatureEncoded, sig).publicKey;
   }

   public static SignedMessage validate(Address address, String message, String signatureBase64)
         throws WrongSignatureException {
      final byte[] signatureEncoded = Base64.decode(signatureBase64);
      if (signatureEncoded == null) {
         // Invalid or truncated base64
         throw new WrongSignatureException(String.format("given signature is not valid base64 %s", signatureBase64));
      }
      final Signature sig = decodeSignature(signatureEncoded);
      final RecoveryInfo info = recoverFromSignature(message, signatureEncoded, sig);
      validateAddressMatches(address, info.publicKey);
      return new SignedMessage(sig, info.publicKey, info.recId);
   }

   public static void validateAddressMatches(Address address, PublicKey key) throws WrongSignatureException {
      Address recoveredAddress = key.toAddress(address.getNetwork());
      if (!address.equals(recoveredAddress)) {
         throw new WrongSignatureException(String.format("given Address did not match \nexpected %s\n but got %s",
               address, recoveredAddress));
      }
   }

   /*
    * to avoid extracting Signature sig twice, the parsed version is also passed
    * in here. it must be obtained from signatureEncoded
    */
   private static RecoveryInfo recoverFromSignature(String message, byte[] signatureEncoded, Signature sig)
         throws WrongSignatureException {
      int header = signatureEncoded[0] & 0xFF;
      // The header byte: 0x1B = first key with even y, 0x1C = first key with
      // odd y,
      // 0x1D = second key with even y, 0x1E = second key with odd y
      if (header < 27 || header > 34)
         throw new WrongSignatureException("Header byte out of range: " + header);

      byte[] messageBytes = Signatures.formatMessageForSigning(message);
      // Note that the C++ code doesn't actually seem to specify any character
      // encoding. Presumably it's whatever
      // JSON-SPIRIT hands back. Assume UTF-8 for now.
      Sha256Hash messageHash = HashUtils.doubleSha256(messageBytes);
      boolean compressed = false;
      if (header >= 31) {
         compressed = true;
         header -= 4;
      }
      int recId = header - 27;
      PublicKey ret = recoverFromSignature(recId, sig, messageHash, compressed);
      if (ret == null) {
         throw new WrongSignatureException("Could not recover public key from signature");
      }
      return new RecoveryInfo(ret, recId);
   }

   private static Signature decodeSignature(byte[] signatureEncoded) throws WrongSignatureException {
      // Parse the signature bytes into r/s and the selector value.
      if (signatureEncoded.length < 65)
         throw new WrongSignatureException("Signature truncated, expected 65 bytes and got " + signatureEncoded.length);
      BigInteger r = new BigInteger(1, BitUtils.copyOfRange(signatureEncoded, 1, 33));
      BigInteger s = new BigInteger(1, BitUtils.copyOfRange(signatureEncoded, 33, 65));
      return new Signature(r, s);
   }

   public static SignedMessage from(Signature signature, PublicKey publicKey, int recId) {
      return new SignedMessage(signature, publicKey, recId);
   }

   /**
    * 

* Given the components of a signature and a selector value, recover and * return the public key that generated the signature according to the * algorithm in SEC1v2 section 4.1.6. *

*

* The recId is an index from 0 to 3 which indicates which of the 4 possible * keys is the correct one. Because the key recovery operation yields * multiple potential keys, the correct key must either be stored alongside * the signature, or you must be willing to try each recId in turn until you * find one that outputs the key you are expecting. *

*

* If this method returns null it means recovery was not possible and recId * should be iterated. *

*

* Given the above two points, a correct usage of this method is inside a for * loop from 0 to 3, and if the output is null OR a key that is not the one * you expect, you try again with the next recId. *

* * @param recId Which possible key to recover. * @param sig the R and S components of the signature, wrapped. * @param message Hash of the data that was signed. * @param compressed Whether or not the original pubkey was compressed. * @return PublicKey or null if recovery was not possible */ public static PublicKey recoverFromSignature(int recId, Signature sig, Sha256Hash message, boolean compressed) { Preconditions.checkArgument(recId >= 0, "recId must be positive"); Preconditions.checkArgument(sig.r.compareTo(BigInteger.ZERO) >= 0, "r must be positive"); Preconditions.checkArgument(sig.s.compareTo(BigInteger.ZERO) >= 0, "s must be positive"); Preconditions.checkNotNull(message); // 1.0 For j from 0 to h (h == recId here and the loop is outside this // function) // 1.1 Let x = r + jn BigInteger n = Parameters.n; // Curve order. BigInteger i = BigInteger.valueOf((long) recId / 2); BigInteger x = sig.r.add(i.multiply(n)); // 1.2. Convert the integer x to an octet string X of length mlen using // the conversion routine // specified in Section 2.3.7, // // 1.3. Convert the octet string (16 set binary digits)||X to an elliptic // curve point R using the // conversion routine specified in Section 2.3.4. If this conversion // routine outputs "invalid", then // do another iteration of Step 1. // // More concisely, what these points mean is to use X as a compressed // public key. Curve curve = Parameters.curve; BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent about // the letter it uses for the prime. if (x.compareTo(prime) >= 0) { // Cannot have point co-ordinates larger than this as everything takes // place modulo Q. return null; } // Compressed keys require you to know an extra bit of data about the // y-coord as there are two possibilities. // So it's encoded in the recId. Point R = EcTools.decompressKey(x, (recId & 1) == 1); // 1.4. If nR != point at infinity, then do another iteration of Step 1 // (callers responsibility). if (!R.multiply(n).isInfinity()) return null; // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature // verification. BigInteger e = new BigInteger(1, message.getBytes()); // 1.6. For k from 1 to 2 do the following. (loop is outside this function // via iterating recId) // 1.6.1. Compute a candidate public key as: // Q = mi(r) * (sR - eG) // // Where mi(x) is the modular multiplicative inverse. We transform this // into the following: // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) // Where -e is the modular additive inverse of e, that is z such that z + // e = 0 (mod n). In the above equation // ** is point multiplication and + is point addition (the EC group // operator). // // We can find the additive inverse by subtracting e from zero then taking // the mod. For example the additive // inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod 11 = // 8. BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n); BigInteger rInv = sig.r.modInverse(n); BigInteger srInv = rInv.multiply(sig.s).mod(n); BigInteger eInvrInv = rInv.multiply(eInv).mod(n); Point q = EcTools.sumOfTwoMultiplies(Parameters.G, eInvrInv, R, srInv); if (compressed) { // We have to manually recompress the point as the compressed-ness gets // lost when multiply() is used. q = new Point(curve, q.getX(), q.getY(), true); } return new PublicKey(q.getEncoded()); } /* * public static SignedMessage from(byte[] signature, PublicKey publicKey) { * ByteReader reader = new ByteReader(signature); Signature sig = * Signatures.decodeSignatureParameters(reader); * Preconditions.checkState(reader.available() == 0); return new * SignedMessage(sig, publicKey, recId); } */ public byte[] bitcoinEncodingOfSignature() { if (recId == -1) throw new RuntimeException("Could not construct a recoverable key. This should never happen."); int headerByte = recId + 27 + (getPublicKey().isCompressed() ? 4 : 0); byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for // S sigData[0] = (byte) headerByte; System.arraycopy(EcTools.integerToBytes(signature.r, 32), 0, sigData, 1, 32); System.arraycopy(EcTools.integerToBytes(signature.s, 32), 0, sigData, 33, 32); return sigData; } public PublicKey getPublicKey() { return publicKey; } public String getBase64Signature() { return Base64.encodeToString(bitcoinEncodingOfSignature(), false); } public byte[] getDerEncodedSignature(){ //byte[] rsValues = bitcoinEncodingOfSignature(); byte[] rBytes = signature.r.toByteArray(); byte[] sBytes = signature.s.toByteArray(); ByteWriter rsValues = new ByteWriter(rBytes.length + sBytes.length + 2 + 2); rsValues.put((byte) 0x02); // Type: integer rsValues.put((byte)rBytes.length); // length rsValues.putBytes(rBytes); // data rsValues.put((byte) 0x02); // Type: integer rsValues.put((byte)sBytes.length); // length rsValues.putBytes(sBytes); // data ByteWriter byteWriter = new ByteWriter(2 + rsValues.length()); byteWriter.put((byte)0x30); // Tag Preconditions.checkState(rsValues.length() <= 255, "total length should be smaller than 256"); byteWriter.put((byte) rsValues.length()); byteWriter.putBytes(rsValues.toBytes()); return byteWriter.toBytes(); } public static class RecoveryInfo { PublicKey publicKey; int recId; private RecoveryInfo(PublicKey publicKey, int recId) { this.publicKey = publicKey; this.recId = recId; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy