commonMain.fr.acinq.bitcoin.BtcSerializer.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bitcoin-kmp Show documentation
Show all versions of bitcoin-kmp Show documentation
A simple Kotlin Multiplatform library which implements most of the bitcoin protocol
The newest version!
/*
* Copyright 2020 ACINQ SAS
*
* 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 fr.acinq.bitcoin
import fr.acinq.bitcoin.crypto.Pack
import fr.acinq.bitcoin.io.ByteArrayInput
import fr.acinq.bitcoin.io.ByteArrayOutput
import fr.acinq.bitcoin.io.Input
import fr.acinq.bitcoin.io.Output
import fr.acinq.secp256k1.Hex
import kotlin.jvm.JvmStatic
public abstract class BtcSerializer {
/**
* write a message to a stream
*
* @param message message
* @param out output stream
*/
public abstract fun write(message: T, out: Output, protocolVersion: Long)
public fun write(message: T, out: Output): Unit = write(message, out, Protocol.PROTOCOL_VERSION)
/**
* write a message to a byte array
*
* @param message message
* @return a serialized message
*/
public fun write(message: T, protocolVersion: Long): ByteArray {
val out = ByteArrayOutput()
write(message, out, protocolVersion)
return out.toByteArray()
}
public open fun write(message: T): ByteArray = write(message, Protocol.PROTOCOL_VERSION)
/**
* read a message from a stream
*
* @param input input stream
* @return a deserialized message
*/
public abstract fun read(input: Input, protocolVersion: Long): T
public fun read(input: Input): T = read(input, Protocol.PROTOCOL_VERSION)
/**
* read a message from a byte array
*
* @param input serialized message
* @return a deserialized message
*/
public fun read(input: ByteArray, protocolVersion: Long): T = read(ByteArrayInput(input), protocolVersion)
public open fun read(input: ByteArray): T = read(input, Protocol.PROTOCOL_VERSION)
/**
* read a message from a hex string
*
* @param input message binary data in hex format
* @return a deserialized message of type T
*/
public fun read(input: String, protocolVersion: Long): T = read(Hex.decode(input), protocolVersion)
public open fun read(input: String): T = read(input, Protocol.PROTOCOL_VERSION)
public open fun validate(message: T) {}
public companion object {
@JvmStatic
public fun uint8(input: Input): UByte {
require(input.availableBytes >= 1)
return input.read().toUByte()
}
@JvmStatic
public fun writeUInt8(input: UByte, out: Output): Unit = out.write(input.toInt() and 0xff)
@JvmStatic
public fun uint16(input: Input): UShort = Pack.int16LE(input).toUShort()
@JvmStatic
public fun uint16(input: ByteArray): UShort = Pack.int16LE(input).toUShort()
@JvmStatic
public fun writeUInt16(input: UShort, out: Output): Unit = Pack.writeInt16LE(input.toShort(), out)
@JvmStatic
public fun writeUInt16(input: UShort): ByteArray = Pack.writeInt16LE(input.toShort())
@JvmStatic
public fun uint32(input: Input): UInt = Pack.int32LE(input).toUInt()
@JvmStatic
public fun uint32(input: ByteArray): UInt = Pack.int32LE(input).toUInt()
@JvmStatic
public fun writeUInt32(input: UInt, out: Output): Unit = Pack.writeInt32LE(input.toInt(), out)
@JvmStatic
public fun writeUInt32(input: UInt): ByteArray = Pack.writeInt32LE(input.toInt())
@JvmStatic
public fun uint64(input: Input): ULong = Pack.int64LE(input).toULong()
@JvmStatic
public fun uint64(input: ByteArray): ULong = Pack.int64LE(input).toULong()
@JvmStatic
public fun writeUInt64(input: ULong, out: Output): Unit = Pack.writeInt64LE(input.toLong(), out)
@JvmStatic
public fun writeUInt64(input: ULong): ByteArray = Pack.writeInt64LE(input.toLong())
@JvmStatic
public fun varint(blob: ByteArray): ULong = varint(ByteArrayInput(blob))
@JvmStatic
public fun varint(input: Input): ULong {
val first = input.read()
return when {
first < 0xFD -> first.toULong()
first == 0xFD -> uint16(input).toULong()
first == 0xFE -> uint32(input).toULong()
first == 0xFF -> uint64(input)
else -> {
throw IllegalArgumentException("invalid first byte $first for varint type")
}
}
}
@JvmStatic
public fun writeVarint(input: Int, out: Output): Unit = writeVarint(input.toULong(), out)
@JvmStatic
public fun writeVarint(input: ULong, out: Output) {
when {
input < 253uL -> writeUInt8(input.toUByte(), out)
input <= 65535uL -> {
writeUInt8(0xFDu, out)
writeUInt16(input.toUShort(), out)
}
input <= 4294967295uL -> {
writeUInt8(0xFEu, out)
writeUInt32(input.toUInt(), out)
}
else -> {
writeUInt8(0xFFu, out)
writeUInt64(input, out)
}
}
}
@JvmStatic
public fun bytes(input: Input, size: Long): ByteArray = bytes(input, size.toInt())
@JvmStatic
public fun bytes(input: Input, size: Int): ByteArray {
// NB: we make that check before allocating a byte array, otherwise an attacker can exhaust our heap space.
require(size <= input.availableBytes) { "cannot read $size bytes from a stream that has ${input.availableBytes} bytes left" }
val blob = ByteArray(size)
if (size > 0) {
input.read(blob, 0, size)
}
return blob
}
@JvmStatic
public fun writeBytes(input: ByteArray, out: Output): Unit = out.write(input)
@JvmStatic
public fun writeBytes(input: ByteVector, out: Output): Unit = writeBytes(input.toByteArray(), out)
@JvmStatic
public fun writeBytes(input: ByteVector32, out: Output): Unit = writeBytes(input.toByteArray(), out)
@JvmStatic
public fun varstring(input: Input): String {
val length = varint(input)
val bytes = bytes(input, length.toInt())
val chars = bytes.map { it.toInt().toChar() }.toCharArray()
return chars.concatToString()
}
@JvmStatic
public fun writeVarstring(input: String, out: Output) {
writeVarint(input.length, out)
writeBytes(input.encodeToByteArray(), out)
}
@JvmStatic
public fun hash(input: Input): ByteArray = bytes(input, 32) // a hash is always 256 bits
@JvmStatic
public fun script(input: Input): ByteArray {
val length = varint(input) // read size
return bytes(input, length.toInt()) // read bytes
}
@JvmStatic
public fun writeScript(input: ByteArray, out: Output) {
writeVarint(input.size, out)
writeBytes(input, out)
}
@JvmStatic
public fun writeScript(input: ByteVector, out: Output) {
writeScript(input.toByteArray(), out)
}
public fun readCollection(
input: Input,
reader: BtcSerializer,
maxElement: Int?,
protocolVersion: Long
): List = readCollection(input, reader::read, maxElement, protocolVersion)
public fun readCollection(
input: Input,
reader: (Input, Long) -> T,
maxElement: Int?,
protocolVersion: Long
): List {
val count = varint(input).toInt()
if (maxElement != null) require(count <= maxElement) { "invalid length" }
val items = mutableListOf()
for (i in 1..count) {
items += reader(input, protocolVersion)
}
return items.toList()
}
public fun readCollection(input: Input, reader: BtcSerializer, protocolVersion: Long): List =
readCollection(input, reader, null, protocolVersion)
public fun writeCollection(
seq: List,
output: Output,
writer: BtcSerializer,
protocolVersion: Long
): Unit = writeCollection(seq, output, writer::write, protocolVersion)
public fun writeCollection(
seq: List,
output: Output,
writer: (T, Output, Long) -> Unit,
protocolVersion: Long
) {
writeVarint(seq.size, output)
seq.forEach { writer.invoke(it, output, protocolVersion) }
}
}
}
public interface BtcSerializable {
public fun serializer(): BtcSerializer
}