package org.bitseal.crypt; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.security.Signature; import java.security.SignatureException; import org.bitseal.core.AddressProcessor; import org.bitseal.data.Pubkey; import org.bitseal.data.UnencryptedMsg; import org.bitseal.util.ArrayCopier; import org.bitseal.util.ByteFormatter; import org.bitseal.util.ByteUtils; import org.bitseal.util.VarintEncoder; import org.spongycastle.jce.interfaces.ECPrivateKey; import org.spongycastle.jce.interfaces.ECPublicKey; import android.util.Log; /** * Offers methods for creating and verifying ECDSA signatures. * * @author Jonathan Coe */ public class SigProcessor { private static final String ALGORITHM = "ECDSA"; private static final String PROVIDER = "SC"; // Spongy Castle private static final String TAG = "SIG_PROCESSOR"; /** * Constructs the payload necessary to sign or verify the signature of a PubKey * * @param pubkey - The PubKey object that we wish to sign or verify the signature of * * @return A byte[] containing the constructed payload. */ public byte[] createPubkeySignaturePayload(Pubkey pubkey) { byte[] payload = null; ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { outputStream.write(ByteUtils.longToBytes(pubkey.getExpirationTime())); outputStream.write(ByteUtils.intToBytes(pubkey.getObjectType())); outputStream.write(VarintEncoder.encode(pubkey.getObjectVersion())); outputStream.write(VarintEncoder.encode(pubkey.getStreamNumber())); if (pubkey.getObjectVersion() >= 4) // Pubkeys of version 4 and above have the address tag included in the signature payload { String addressString = new AddressGenerator().recreateAddressString(pubkey.getObjectVersion(), pubkey.getStreamNumber(), pubkey.getPublicSigningKey(), pubkey.getPublicEncryptionKey()); outputStream.write(new AddressProcessor().calculateAddressTag(addressString)); } outputStream.write(ByteUtils.intToBytes(pubkey.getBehaviourBitfield())); // If the public signing or public encryption key have their leading 0x04 byte in place then we need to remove them byte[] publicSigningKey = pubkey.getPublicSigningKey(); if (publicSigningKey[0] == (byte) 4 && publicSigningKey.length == 65) { publicSigningKey = ArrayCopier.copyOfRange(publicSigningKey, 1, publicSigningKey.length); } outputStream.write(publicSigningKey); byte[] publicEncryptionKey = pubkey.getPublicEncryptionKey(); if (publicEncryptionKey[0] == (byte) 4 && publicEncryptionKey.length == 65) { publicEncryptionKey = ArrayCopier.copyOfRange(publicEncryptionKey, 1, publicEncryptionKey.length); } outputStream.write(publicEncryptionKey); outputStream.write(VarintEncoder.encode(pubkey.getNonceTrialsPerByte())); outputStream.write(VarintEncoder.encode(pubkey.getExtraBytes())); payload = outputStream.toByteArray(); } catch (IOException e) { throw new RuntimeException("IOException occurred in SigProcessor.createPubkeySignaturePayload()", e); } return payload; } /** * Constructs the payload necessary to sign or verify the signature of an UnencryptedMsg * * @param unencMsg - The UnencryptedMsg object that we wish to sign or verify the signature of * * @return A byte[] containing the constructed payload. */ public byte[] createUnencryptedMsgSignaturePayload(UnencryptedMsg unencMsg) { byte[] payload = null; ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { outputStream.write(ByteUtils.longToBytes(unencMsg.getExpirationTime())); outputStream.write(ByteUtils.intToBytes(unencMsg.getObjectType())); outputStream.write(VarintEncoder.encode(unencMsg.getObjectVersion())); outputStream.write(VarintEncoder.encode(unencMsg.getStreamNumber())); outputStream.write(VarintEncoder.encode(unencMsg.getSenderAddressVersion())); outputStream.write(VarintEncoder.encode(unencMsg.getSenderStreamNumber())); outputStream.write(ByteUtils.intToBytes(unencMsg.getBehaviourBitfield())); // If the public signing and public encryption keys have their leading 0x04 byte in place then we need to remove them byte[] publicSigningKey = unencMsg.getPublicSigningKey(); if (publicSigningKey[0] == (byte) 4 && publicSigningKey.length == 65) { publicSigningKey = ArrayCopier.copyOfRange(publicSigningKey, 1, publicSigningKey.length); } outputStream.write(publicSigningKey); byte[] publicEncryptionKey = unencMsg.getPublicEncryptionKey(); if (publicEncryptionKey[0] == (byte) 4 && publicEncryptionKey.length == 65) { publicEncryptionKey = ArrayCopier.copyOfRange(publicEncryptionKey, 1, publicEncryptionKey.length); } outputStream.write(publicEncryptionKey); if (unencMsg.getSenderAddressVersion() >= 3) // The nonceTrialsPerByte and extraBytes fields are only included when the address version is >= 3 { outputStream.write(VarintEncoder.encode(unencMsg.getNonceTrialsPerByte())); outputStream.write(VarintEncoder.encode(unencMsg.getExtraBytes())); } // For the purposes of signature payloads, the ripe hash must always be 20 bytes in length. // Therefore if it is less than 20 bytes in length, pad it with zero bytes until it is. byte[] ripeHash = unencMsg.getDestinationRipe(); ripeHash = ByteUtils.padWithLeadingZeros(ripeHash, 20); outputStream.write(ripeHash); outputStream.write(VarintEncoder.encode(unencMsg.getEncoding())); outputStream.write(VarintEncoder.encode(unencMsg.getMessageLength())); outputStream.write(unencMsg.getMessage()); outputStream.write(VarintEncoder.encode(unencMsg.getAckLength())); outputStream.write(unencMsg.getAckMsg()); payload = outputStream.toByteArray(); outputStream.close(); } catch (IOException e) { throw new RuntimeException("IOException occurred in SigProcessor.createUnencryptedMsgSignaturePayload()", e); } return payload; } /** * Checks whether a given ECDSA signature is valid. * * @param payloadToVerify - The payload which we want to verify the signature of * @param signature - A byte[] containing the signature to be verified * @param publicKey - The ECPublicKey object used to create the signature * * @return A boolean indicating whether the pubkey's signature is valid or not */ public boolean verifySignature(byte[] payloadToVerify, byte[] signature, ECPublicKey publicKey) { boolean signatureValid = false; try { Signature sig = Signature.getInstance(ALGORITHM, PROVIDER); sig.initVerify(publicKey); sig.update(payloadToVerify); signatureValid = sig.verify(signature); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("NoSuchAlgorithmException occurred in SigProcessor.verifySignature()", e); } catch (NoSuchProviderException e) { throw new RuntimeException("NoSuchProviderException occurred in SigProcessor.verifySignature()", e); } catch (SignatureException e) { throw new RuntimeException("SignatureException occurred in SigProcessor.verifySignature()", e); } catch (InvalidKeyException e) { throw new RuntimeException("InvalidKeyException occurred in SigProcessor.verifySignature()", e); } if (signatureValid == false) { Log.e(TAG, "While running SigProc.verifySignature(), the following signature was found to be invalid:\n" + "Invalid signature: " + ByteFormatter.byteArrayToHexString(signature) + "\n" + "Length of invalid signature: " + signature.length + " bytes" + "\n" + "Payload for which the signature was invalid: " + ByteFormatter.byteArrayToHexString(payloadToVerify)); } return signatureValid; } /** * Produces an ECDSA signature for a given payload, using a private key in Wallet Import * Format to produce the signature. * * @param payloadToSign - The payload to be signed * @param wifPrivateKey - A String containing the private key which will be used to create the * signature, encoded in Bitcoin-style Wallet Import Format. * * @return A byte[] containing the newly created signature. */ public byte[] signWithWIFKey(byte[] payloadToSign, String wifPrivateKey) { KeyConverter converter = new KeyConverter(); ECPrivateKey privKey = converter.decodePrivateKeyFromWIF(wifPrivateKey); byte[] signature = sign(payloadToSign, privKey); return signature; } /** * Produces a ECDSA signature for a given payload, using a private key to produce the signature. * * @param payloadToSign - The payload to be signed * @param privateKey - The ECPrivateKey object which will be used to create the signature. * * @return A byte[] containing the newly created signature. */ private byte[] sign(byte[] payloadToSign, ECPrivateKey privateKey) { byte [] signature = null; try { Signature sig = Signature.getInstance(ALGORITHM, PROVIDER); sig.initSign(privateKey, new SecureRandom()); sig.update(payloadToSign); signature = sig.sign(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("NoSuchAlgorithmException occurred in SigProcessor.sign()", e); } catch (NoSuchProviderException e) { throw new RuntimeException("NoSuchProviderException occurred in SigProcessor.sign()", e); } catch (InvalidKeyException e) { throw new RuntimeException("InvalidKeyException occurred in SigProcessor.sign()", e); } catch (SignatureException e) { throw new RuntimeException("SignatureException occurred in SigProcessor.sign()", e); } return signature; } }