org.coinspark.protocol.CoinSparkBase Maven / Gradle / Ivy
/*
* CoinSpark 2.1 - Java library
*
* Copyright (c) Coin Sciences Ltd
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.coinspark.protocol;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
* Base class implementing static utility functions and classes/utility functions used internally.
*/
public class CoinSparkBase {
// Public constants
protected static final byte COINSPARK_GENESIS_PREFIX = 'g';
protected static final byte COINSPARK_TRANSFERS_PREFIX = 't';
protected static final byte COINSPARK_PAYMENTREF_PREFIX = 'r';
protected static final byte COINSPARK_MESSAGE_PREFIX = 'm';
protected final static long COINSPARK_SATOSHI_QTY_MAX = 2100000000000000L;
// General public functions for managing CoinSpark metadata and bitcoin transaction output scripts
/**
* Extracts OP_RETURN metadata (not necessarily CoinSpark data) from a bitcoin tx output script.
*
* @param scriptPubKey Output script as hexadecimal.
* @return byte [] | null Raw binary embedded metadata if found, null otherwise.
*/
public static byte [] scriptToMetadata(String scriptPubKey)
{
return scriptToMetadata(hexToByte(scriptPubKey));
}
/**
* Extracts OP_RETURN metadata (not necessarily CoinSpark data) from a bitcoin tx output script.
*
* @param scriptPubKey Output script as raw binary data.
* @return byte [] | null Raw binary embedded metadata if found, null otherwise.
*/
public static byte [] scriptToMetadata(byte[] scriptPubKey)
{
if(scriptPubKey == null)
return null;
int scriptPubKeyLen = scriptPubKey.length;
int metadataLength = scriptPubKeyLen-2; // Skip the signature
if ( (scriptPubKeyLen>2) && (scriptPubKey[0]==0x6a) &&
(scriptPubKey[1]>0) && (scriptPubKey[1]<=75) && (scriptPubKey[1] == metadataLength))
{
return Arrays.copyOfRange(scriptPubKey, 2, scriptPubKeyLen);
}
return null;
}
/**
* Extracts OP_RETURN metadata (not necessarily CoinSpark data) from a bitcoin tx output scripts.
*
* @param scriptPubKeys Output scripts as hexadecimal.
* @return metadata if found, null otherwise
*/
public static byte [] scriptsToMetadata(String [] scriptPubKeys)
{
byte [][] raw=new byte[scriptPubKeys.length][];
for(int i=0;i=scriptLength) ) {
{
int scriptRawLen = metadata.length+2;
scriptPubKey=new byte[scriptRawLen];
scriptPubKey[0]=0x6a;
scriptPubKey[1] = (byte)metadata.length;
System.arraycopy(metadata, 0, scriptPubKey, 2, metadata.length);
return scriptPubKey;
}
return null;
}
/**
* Calculates the maximum length of CoinSpark metadata that can be added to some existing CoinSpark metadata
* to fit into a specified number of bytes.
*
* The calculation is not simply metadataMaxLen-metadata.length because some space is saved when combining pieces of CoinSpark metadata together.
*
* @param metadata The existing CoinSpark metadata in raw binary form, which can itself already be
* a combination of more than one CoinSpark metadata element.
* @param metadataMaxLen The total number of bytes available for the combined metadata.
* @return integer The number of bytes which are available for the new piece of metadata to be added.
*/
public static int metadataMaxAppendLen(byte [] metadata, int metadataMaxLen)
{
return Math.max(metadataMaxLen - (metadata.length + 1 - COINSPARK_METADATA_IDENTIFIER_LEN), 0);
}
/**
* Appends one piece of CoinSpark metadata to another.
*
* @param metadata The existing CoinSpark metadata in raw binary form (or null), which can itself already be
* a combination of more than one CoinSpark metadata element.
* @param metadataMaxLen The total number of bytes available for the combined metadata.
* @param appendMetadata The new CoinSpark metadata to be appended, in raw binary form.
* @return byte [] | null The combined CoinSpark metadata as raw binary, or null if we failed.
*/
public static byte [] metadataAppend(byte [] metadata, int metadataMaxLen, byte [] appendMetadata)
{
if(metadata == null) // metadata == null case
{
if(metadataMaxLen < appendMetadata.length) // check append metdata is short enough
return null;
return appendMetadata;
}
CoinSparkBase.CoinSparkBuffer oldBuffer=new CoinSparkBase().new CoinSparkBuffer(metadata);
if(!oldBuffer.locateRange((byte)0)) // check we can find last metadata
return null;
if(appendMetadata.length < COINSPARK_METADATA_IDENTIFIER_LEN + 1) // check there is enough to check the prefix
return null;
if (memcmp(COINSPARK_METADATA_IDENTIFIER.getBytes(), appendMetadata, COINSPARK_METADATA_IDENTIFIER_LEN) != 0) // check the prefix
return null;
int needLength=metadata.length+appendMetadata.length-COINSPARK_METADATA_IDENTIFIER_LEN+1;
if(metadataMaxLen < needLength) // check there is enough space
return null;
int lastMetadataLen=oldBuffer.availableForRead()+1; // include prefix
int lastMetaDataPos=oldBuffer.offsetRead-1;
CoinSparkBase.CoinSparkBuffer newBuffer=new CoinSparkBase().new CoinSparkBuffer();
newBuffer.writeBytes(metadata, lastMetaDataPos); // Data before last metadata
newBuffer.writeByte((byte)lastMetadataLen); // Length prefix for last metadata
newBuffer.writeByte(metadata[lastMetaDataPos]); // Last metadata prefix
newBuffer.writeBytes(oldBuffer.readBytes(oldBuffer.availableForRead()));// Last metadata without identifier and prefix
newBuffer.writeBytes(Arrays.copyOfRange(appendMetadata, COINSPARK_METADATA_IDENTIFIER_LEN, appendMetadata.length));// Appended metadata
return newBuffer.toBytes();
}
/**
* Tests whether a bitcoin tx output script is 'regular', i.e. not an OP_RETURN script.
*
* This function will declare empty scripts or invalid hex scripts as 'regular' as well, since they are not OP_RETURNs.
* Use this to build $outputsRegular arrays which are used by various other functions.
*
* @param scriptPubKey Output script as hexadecimal.
* @return true if the script is 'regular', false if it is an OP_RETURN script.
*/
public static boolean scriptIsRegular(String scriptPubKey)
{
return scriptIsRegular(hexToByte(scriptPubKey));
}
/**
* Tests whether a bitcoin tx output script is 'regular', i.e. not an OP_RETURN script.
*
* This function will declare empty scripts or invalid hex scripts as 'regular' as well, since they are not OP_RETURNs.
* Use this to build $outputsRegular arrays which are used by various other functions.
*
* @param scriptPubKey Output script as raw binary data.
* @return true if the script is 'regular', false if it is an OP_RETURN script.
*/
public static boolean scriptIsRegular(byte[] scriptPubKey)
{
return (scriptPubKey.length < 1) || (scriptPubKey[0] != 0x6a);
}
// Utitlity functions/classes used internally in CoinSpark Library
private static final long COINSPARK_FEE_BASIS_MAX_SATOSHIS = 1000;
protected static long getMinFeeBasis(long[] outputsSatoshis, boolean[] outputsRegular)
{
if(outputsSatoshis.length != outputsRegular.length)
{
return COINSPARK_SATOSHI_QTY_MAX;
}
int countOutputs=outputsRegular.length;
long smallestOutputSatoshis = COINSPARK_SATOSHI_QTY_MAX;
for (int outputIndex=0; outputIndex outputsSatoshis[outputIndex])
smallestOutputSatoshis = outputsSatoshis[outputIndex];
}
if (smallestOutputSatoshis > COINSPARK_FEE_BASIS_MAX_SATOSHIS)
smallestOutputSatoshis = COINSPARK_FEE_BASIS_MAX_SATOSHIS;
return smallestOutputSatoshis;
}
protected final static String COINSPARK_METADATA_IDENTIFIER = "SPK";
private static final byte[] hexCharMap = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
private static final int[] base58Minus49ToInteger = { // 74 elements
0, 1, 2, 3, 4, 5, 6, 7, 8, -1, -1, -1, -1, -1, -1, -1,
9, 10, 11, 12, 13, 14, 15, 16, -1, 17, 18, 19, 20, 21, -1, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1, -1,
33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57 };
private final static int COINSPARK_METADATA_IDENTIFIER_LEN = 3;
private final static int COINSPARK_LENGTH_PREFIX_MAX = 96;
/**
* Returns SHA256 hash of the input raw data
* @param input input raw data
* @param inputLen actual size of the data to hash (raw data may be longer)
* C function: void CoinSparkCalcSHA256Hash(const unsigned char* input, const size_t inputLen, unsigned char hash[32]);
* @return SHA-256 hash
*/
protected static byte [] coinSparkCalcSHA256Hash(byte[] input, int inputLen)
{
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return digest.digest(Arrays.copyOf(input, inputLen));
} catch (NoSuchAlgorithmException ex) {
return null;
}
}
protected static int base58ToInteger(byte base58Character) // returns -1 if invalid
{
if ( (base58Character<49) || (base58Character>122) )
return -1;
return base58Minus49ToInteger[base58Character-49];
}
protected static String byteToHex(byte [] raw)
{
if(raw==null)
return null;
byte [] hexBytes=new byte [raw.length*2];
for(int i=0;i>4)];
hexBytes[2*i+1]=hexCharMap[(byte)(value&15)];
}
return new String(hexBytes);
}
protected static byte[] hexToByte(String str)
{
if (str == null)
return null;
byte[] bytes = new byte[str.length() / 2];
for (int i = 0; i < bytes.length; i++)
{
bytes[i] = (byte) Integer
.parseInt(str.substring(2 * i, 2 * i + 2), 16);
}
return bytes;
}
protected static byte[] unsignedToSmallEndianBytes(long value, int bytes)
{
if (bytes <= 0)
return null;
byte [] raw=new byte[bytes];
for (int index=0; indexdataBuffer.length)
return null;
long value = 0;
for (int curbyte=bytes-1; curbyte>=0; curbyte--)
{
value *= 256;
int ttt = dataBuffer[curbyte + offset];
if (ttt < 0) ttt &= 0xFF;
value += ttt;
}
return value & 0x7FFFFFFFFFFFFFFFL;
}
protected static int memcmp(byte b1[], byte b2[], int sz){
for(int i = 0; i < sz; i++){
if(b1[i] != b2[i]){
if((b1[i] >= 0 && b2[i] >= 0)||(b1[i] < 0 && b2[i] < 0))
return b1[i] - b2[i];
if(b1[i] < 0 && b2[i] >= 0)
return 1;
if(b2[i] < 0 && b1[i] >=0)
return -1;
}
}
return 0;
}
protected static int getLastRegularOutput(boolean[] outputsRegular)
{
int countOutputs=outputsRegular.length;
int outputIndex;
for (outputIndex=countOutputs-1; outputIndex>=0; outputIndex--)
{
if (outputsRegular[outputIndex])
return outputIndex;
}
return countOutputs; // indicates no regular ones were found
}
protected class CoinSparkBuffer{
private final static int BUFFER_ALLOC_LENGTH = 40;
private boolean resizable;
private byte [] raw;
private int offsetRead;
private int offsetWrite;
private int sizeRead;
public CoinSparkBuffer(int size)
{
raw=new byte[size];
offsetRead=0;
offsetWrite=0;
sizeRead=offsetWrite;
resizable=false;
}
CoinSparkBuffer()
{
this(BUFFER_ALLOC_LENGTH);
resizable=true;
}
CoinSparkBuffer(String Source,boolean IsHex)
{
if(IsHex)
{
raw=hexToByte(Source);
}
else
{
raw=Source.getBytes();
}
offsetRead=0;
offsetWrite=raw.length;
sizeRead=offsetWrite;
resizable=false;
}
CoinSparkBuffer(byte [] Source)
{
raw=Source;
offsetRead=0;
offsetWrite=raw.length;
sizeRead=offsetWrite;
resizable=false;
}
protected int length()
{
return offsetWrite;
}
protected int availableForRead()
{
return sizeRead-offsetRead;
}
protected void resetReadOffset()
{
offsetRead=0;
sizeRead=offsetWrite;
}
protected void resetWriteOffset()
{
offsetWrite=0;
}
protected boolean realloc(int bytes)
{
int size=raw.length;
while(sizeraw.length)
{
if(!resizable)
{
return false;
}
byte [] newRaw=new byte[size];
System.arraycopy(raw, 0, newRaw, 0, offsetWrite);
raw=newRaw;
}
return true;
}
protected byte [] toBytes()
{
if(offsetWrite==0)
{
return null;
}
return Arrays.copyOf(raw, offsetWrite);
}
protected String toAscii()
{
return new String(toBytes());
}
protected String toHex()
{
return byteToHex(toBytes());
}
protected boolean writeByte(byte b)
{
if(!realloc(1))
return false;
raw[offsetWrite]=b;
offsetWrite++;
return true;
}
protected boolean writeInt(int value,int size)
{
return writeLong(value, size);
}
protected boolean writeLong(long value,int size)
{
return writeBytes(unsignedToSmallEndianBytes(value, size));
}
protected boolean writeBytes(byte [] b)
{
if(b==null)
return true;
return writeBytes(b,b.length);
}
protected boolean writeBytes(byte [] b,int size)
{
if(size<=0)
return true;
if(!realloc(size))
return false;
System.arraycopy(b, 0, raw, offsetWrite, size);
offsetWrite+=size;
return true;
}
protected boolean writeString(String s)
{
return writeBytes(s.getBytes());
}
protected Byte readByte()
{
if(offsetRead>=sizeRead)
return null;
offsetRead++;
return raw[offsetRead-1];
}
protected Long readLong(int size)
{
if(offsetRead+size>sizeRead)
return null;
offsetRead+=size;
return SmallEndianBytesToUnsigned(raw, offsetRead-size, size);
}
protected Integer readInt(int size)
{
if(size>4)
return null;
if(offsetRead+size>sizeRead)
return null;
return readLong(size).intValue();
}
protected byte [] readBytes(int size)
{
if(offsetRead+size>sizeRead)
return null;
offsetRead+=size;
return Arrays.copyOfRange(raw, offsetRead-size, offsetRead);
}
protected boolean canRead(int size)
{
if(offsetRead+size>sizeRead)
return false;
return true;
}
protected boolean locateRange(byte desiredPrefix)
{
offsetRead=0;
if(!canRead(COINSPARK_METADATA_IDENTIFIER_LEN+1))
return false;
if (memcmp(COINSPARK_METADATA_IDENTIFIER.getBytes(), raw, COINSPARK_METADATA_IDENTIFIER_LEN) != 0) // check it starts 'SPK'
return false;
offsetRead+=COINSPARK_METADATA_IDENTIFIER_LEN; // skip past 'SPK'
while (offsetRead < offsetWrite)
{
byte foundPrefix = readByte(); // read the next prefix
if (desiredPrefix != 0 ? (foundPrefix==desiredPrefix) : (foundPrefix > COINSPARK_LENGTH_PREFIX_MAX))
{
// it's our data from here to the end (if desiredPrefix is 0, it matches the last one whichever it is)
sizeRead=offsetWrite;
return true;
}
if (foundPrefix>COINSPARK_LENGTH_PREFIX_MAX) // it's some other type of data from here to end
return false;
// if we get here it means we found a length byte
if (offsetRead+foundPrefix > offsetWrite) // something went wrong - length indicated is longer than that available
return false;
if (offsetRead >= offsetWrite) // something went wrong - that was the end of the input data
return false;
if (raw[offsetRead] == desiredPrefix)
{ // it's the length of our part
offsetRead++;
sizeRead=offsetRead+foundPrefix-1;
return true;
}
else
{
offsetRead+=foundPrefix;
}
}
return false;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy