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

commonMain.fr.acinq.lightning.serialization.v3.ChannelState.kt Maven / Gradle / Ivy

There is a newer version: 1.8.4
Show newest version
@file:OptIn(ExperimentalSerializationApi::class)
@file:UseSerializers(
    KeyPathKSerializer::class,
    EitherSerializer::class,
    ShaChainSerializer::class,
    BlockHeaderKSerializer::class,
    BlockHashKSerializer::class,
    FundingSignedSerializer::class,
    ChannelUpdateSerializer::class,
    ByteVectorKSerializer::class,
    ByteVector32KSerializer::class,
    ByteVector64KSerializer::class,
    ChannelAnnouncementSerializer::class,
    PublicKeyKSerializer::class,
    PrivateKeyKSerializer::class,
    ShutdownSerializer::class,
    ClosingSignedSerializer::class,
    SatoshiKSerializer::class,
    UpdateAddHtlcSerializer::class,
    CommitSigSerializer::class,
    EncryptedChannelDataSerializer::class,
    ChannelReestablishDataSerializer::class,
    FundingCreatedSerializer::class,
    CommitSigTlvSerializer::class,
    ShutdownTlvSerializer::class,
    ClosingSignedTlvSerializer::class,
    ChannelReadyTlvSerializer::class,
    ChannelReestablishTlvSerializer::class,
    TlvStreamSerializer::class,
    GenericTlvSerializer::class,
    OnionRoutingPacketSerializer::class,
    FeeratePerKwSerializer::class,
    MilliSatoshiSerializer::class,
    UUIDSerializer::class,
    OutPointKSerializer::class,
    TxOutKSerializer::class,
    TransactionKSerializer::class,
)

package fr.acinq.lightning.serialization.v3

import fr.acinq.bitcoin.*
import fr.acinq.bitcoin.utils.Either
import fr.acinq.lightning.*
import fr.acinq.lightning.blockchain.fee.FeeratePerKw
import fr.acinq.lightning.channel.InteractiveTxOutput
import fr.acinq.lightning.channel.SpliceStatus
import fr.acinq.lightning.channel.states.*
import fr.acinq.lightning.channel.states.Closed
import fr.acinq.lightning.channel.states.Negotiating
import fr.acinq.lightning.channel.states.Normal
import fr.acinq.lightning.channel.states.ShuttingDown
import fr.acinq.lightning.channel.states.WaitForRemotePublishFutureCommitment
import fr.acinq.lightning.crypto.ShaChain
import fr.acinq.lightning.transactions.Transactions
import fr.acinq.lightning.utils.UUID
import fr.acinq.lightning.utils.msat
import fr.acinq.lightning.wire.*
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

@Serializer(forClass = FundingSigned::class)
object FundingSignedSerializer

@Serializer(forClass = Shutdown::class)
object ShutdownSerializer

@Serializer(forClass = ClosingSigned::class)
object ClosingSignedSerializer

@Serializer(forClass = ChannelUpdate::class)
object ChannelUpdateSerializer

@Serializer(forClass = ChannelAnnouncement::class)
object ChannelAnnouncementSerializer

object UpdateAddHtlcSerializer : KSerializer {
    @Serializable
    @SerialName("fr.acinq.lightning.wire.UpdateAddHtlc")
    private data class Surrogate(
        val channelId: ByteVector32,
        val id: Long,
        val amountMsat: MilliSatoshi,
        val paymentHash: ByteVector32,
        val cltvExpiry: CltvExpiry,
        val onionRoutingPacket: OnionRoutingPacket,
    )

    override val descriptor: SerialDescriptor = Surrogate.serializer().descriptor

    override fun serialize(encoder: Encoder, value: UpdateAddHtlc) {
        val surrogate = Surrogate(value.channelId, value.id, value.amountMsat, value.paymentHash, value.cltvExpiry, value.onionRoutingPacket)
        return encoder.encodeSerializableValue(Surrogate.serializer(), surrogate)
    }

    override fun deserialize(decoder: Decoder): UpdateAddHtlc {
        val surrogate = decoder.decodeSerializableValue(Surrogate.serializer())
        return UpdateAddHtlc(surrogate.channelId, surrogate.id, surrogate.amountMsat, surrogate.paymentHash, surrogate.cltvExpiry, surrogate.onionRoutingPacket, null)
    }
}

@Serializer(forClass = UpdateFulfillHtlc::class)
object UpdateFulfillHtlcSerializer

@Serializer(forClass = UpdateFailHtlc::class)
object UpdateFailHtlcSerializer

@Serializer(forClass = UpdateFailMalformedHtlc::class)
object UpdateFailMalformedHtlcSerializer

@Serializer(forClass = UpdateFee::class)
object UpdateFeeSerializer

@Serializer(forClass = CommitSig::class)
object CommitSigSerializer

@Serializer(forClass = EncryptedChannelData::class)
object EncryptedChannelDataSerializer

@Serializer(forClass = ChannelReestablish::class)
object ChannelReestablishDataSerializer

@Serializable
internal data class FundingLocked(
    val channelId: ByteVector32,
    val nextPerCommitmentPoint: PublicKey,
)

@Serializer(forClass = FundingCreated::class)
object FundingCreatedSerializer

@Serializer(forClass = ChannelReadyTlv.ShortChannelIdTlv::class)
object ChannelReadyTlvShortChannelIdTlvSerializer

@Serializer(forClass = ClosingSignedTlv.FeeRange::class)
object ClosingSignedTlvFeeRangeSerializer

@Serializer(forClass = ShutdownTlv.ChannelData::class)
object ShutdownTlvChannelDataSerializer

@Serializer(forClass = ShutdownTlv::class)
object ShutdownTlvSerializer

@Serializer(forClass = CommitSigTlv::class)
object CommitSigTlvSerializer

@Serializer(forClass = ClosingSignedTlv::class)
object ClosingSignedTlvSerializer

@Serializer(forClass = ChannelReadyTlv::class)
object ChannelReadyTlvSerializer

@Serializer(forClass = ChannelReestablishTlv::class)
object ChannelReestablishTlvSerializer

@Serializable
data class TlvStreamSurrogate(val records: Set, val unknown: Set = setOf())
class TlvStreamSerializer : KSerializer> {
    private val delegateSerializer = TlvStreamSurrogate.serializer()
    override val descriptor: SerialDescriptor = delegateSerializer.descriptor
    override fun deserialize(decoder: Decoder): TlvStream {
        val o = delegateSerializer.deserialize(decoder)
        @Suppress("UNCHECKED_CAST")
        return TlvStream(o.records.map { it as T }.toSet(), o.unknown)
    }

    override fun serialize(encoder: Encoder, value: TlvStream) = TODO("Not yet implemented")
}

@Serializer(forClass = GenericTlv::class)
object GenericTlvSerializer

@Serializer(forClass = OnionRoutingPacket::class)
object OnionRoutingPacketSerializer

@Serializer(forClass = FeeratePerKw::class)
object FeeratePerKwSerializer

@Serializer(forClass = MilliSatoshi::class)
object MilliSatoshiSerializer

@Serializer(forClass = UUID::class)
object UUIDSerializer

@Serializable
internal sealed class DirectedHtlc {
    abstract val add: UpdateAddHtlc

    fun to(): fr.acinq.lightning.transactions.DirectedHtlc = when (this) {
        is IncomingHtlc -> fr.acinq.lightning.transactions.IncomingHtlc(this.add)
        is OutgoingHtlc -> fr.acinq.lightning.transactions.OutgoingHtlc(this.add)
    }
}

@Serializable
internal data class IncomingHtlc(override val add: UpdateAddHtlc) : DirectedHtlc()

@Serializable
internal data class OutgoingHtlc(override val add: UpdateAddHtlc) : DirectedHtlc()

@Serializable
internal data class CommitmentSpec(
    val htlcs: Set,
    val feerate: FeeratePerKw,
    val toLocal: MilliSatoshi,
    val toRemote: MilliSatoshi
) {
    fun export() = fr.acinq.lightning.transactions.CommitmentSpec(htlcs.map { it.to() }.toSet(), feerate, toLocal, toRemote)

}

@Serializable
internal data class LocalChanges(val proposed: List, val signed: List, val acked: List) {
    fun export() = fr.acinq.lightning.channel.LocalChanges(proposed, signed, acked)
}

@Serializable
internal data class RemoteChanges(val proposed: List, val acked: List, val signed: List) {
    fun export() = fr.acinq.lightning.channel.RemoteChanges(proposed, acked, signed)
}

@Serializable
internal data class HtlcTxAndSigs(
    val txinfo: Transactions.TransactionWithInputInfo.HtlcTx,
    val localSig: ByteVector64,
    val remoteSig: ByteVector64
) {
    fun export() = fr.acinq.lightning.channel.HtlcTxAndSigs(txinfo, localSig, remoteSig)
}

@Serializable
internal data class PublishableTxs(val commitTx: Transactions.TransactionWithInputInfo.CommitTx, val htlcTxsAndSigs: List) {
    fun export() = fr.acinq.lightning.channel.PublishableTxs(commitTx, htlcTxsAndSigs.map { it.export() })
}

@Serializable
internal data class LocalCommit(val index: Long, val spec: CommitmentSpec, val publishableTxs: PublishableTxs) {
    fun export() = fr.acinq.lightning.channel.LocalCommit(index, spec.export(), publishableTxs.export())
}

@Serializable
internal data class RemoteCommit(
    val index: Long,
    val spec: CommitmentSpec,
    val txid: ByteVector32,
    val remotePerCommitmentPoint: PublicKey
) {
    fun export() = fr.acinq.lightning.channel.RemoteCommit(index, spec.export(), TxId(txid), remotePerCommitmentPoint)
}

@Serializable
internal data class WaitingForRevocation(val nextRemoteCommit: RemoteCommit, val sent: CommitSig, val sentAfterLocalCommitIndex: Long, val reSignAsap: Boolean = false)

@Serializable
internal data class LocalCommitPublished(
    val commitTx: Transaction,
    val claimMainDelayedOutputTx: Transactions.TransactionWithInputInfo.ClaimLocalDelayedOutputTx? = null,
    val htlcTxs: Map = emptyMap(),
    val claimHtlcDelayedTxs: List = emptyList(),
    val claimAnchorTxs: List = emptyList(),
    val irrevocablySpent: Map = emptyMap()
) {
    fun export() = fr.acinq.lightning.channel.LocalCommitPublished(commitTx, claimMainDelayedOutputTx, htlcTxs, claimHtlcDelayedTxs, claimAnchorTxs, irrevocablySpent)
}

@Serializable
internal data class RemoteCommitPublished(
    val commitTx: Transaction,
    val claimMainOutputTx: Transactions.TransactionWithInputInfo.ClaimRemoteCommitMainOutputTx? = null,
    val claimHtlcTxs: Map = emptyMap(),
    val claimAnchorTxs: List = emptyList(),
    val irrevocablySpent: Map = emptyMap()
) {
    fun export() = fr.acinq.lightning.channel.RemoteCommitPublished(commitTx, claimMainOutputTx, claimHtlcTxs, claimAnchorTxs, irrevocablySpent)
}

@Serializable
internal data class RevokedCommitPublished(
    val commitTx: Transaction,
    val remotePerCommitmentSecret: PrivateKey,
    val claimMainOutputTx: Transactions.TransactionWithInputInfo.ClaimRemoteCommitMainOutputTx? = null,
    val mainPenaltyTx: Transactions.TransactionWithInputInfo.MainPenaltyTx? = null,
    val htlcPenaltyTxs: List = emptyList(),
    val claimHtlcDelayedPenaltyTxs: List = emptyList(),
    val irrevocablySpent: Map = emptyMap()
) {
    fun export() = fr.acinq.lightning.channel.RevokedCommitPublished(commitTx, remotePerCommitmentSecret, claimMainOutputTx, mainPenaltyTx, htlcPenaltyTxs, claimHtlcDelayedPenaltyTxs, irrevocablySpent)
}

/**
 * README: by design, we do not include channel private keys and secret here, so they won't be included in our backups (local files, encrypted peer backup, ...), so even
 * if these backups were compromised channel private keys would not be leaked unless the main seed was also compromised.
 * This means that they will be recomputed once when we convert serialized data to their "live" counterparts.
 */
@Serializable
internal data class LocalParams constructor(
    val nodeId: PublicKey,
    val fundingKeyPath: KeyPath,
    val dustLimit: Satoshi,
    val maxHtlcValueInFlightMsat: Long,
    val channelReserve: Satoshi,
    val htlcMinimum: MilliSatoshi,
    val toSelfDelay: CltvExpiryDelta,
    val maxAcceptedHtlcs: Int,
    val isFunder: Boolean,
    val defaultFinalScriptPubKey: ByteVector,
    val features: Features
) {
    fun export() = fr.acinq.lightning.channel.LocalParams(
        nodeId,
        fundingKeyPath,
        dustLimit,
        maxHtlcValueInFlightMsat,
        htlcMinimum,
        toSelfDelay,
        maxAcceptedHtlcs,
        isFunder,
        defaultFinalScriptPubKey,
        features
    )
}

@Serializable
internal data class RemoteParams(
    val nodeId: PublicKey,
    val dustLimit: Satoshi,
    val maxHtlcValueInFlightMsat: Long,
    val channelReserve: Satoshi,
    val htlcMinimum: MilliSatoshi,
    val toSelfDelay: CltvExpiryDelta,
    val maxAcceptedHtlcs: Int,
    val fundingPubKey: PublicKey,
    val revocationBasepoint: PublicKey,
    val paymentBasepoint: PublicKey,
    val delayedPaymentBasepoint: PublicKey,
    val htlcBasepoint: PublicKey,
    val features: Features
) {
    fun export() = fr.acinq.lightning.channel.RemoteParams(
        nodeId,
        dustLimit,
        maxHtlcValueInFlightMsat,
        htlcMinimum,
        toSelfDelay,
        maxAcceptedHtlcs,
        revocationBasepoint,
        paymentBasepoint,
        delayedPaymentBasepoint,
        htlcBasepoint,
        features
    )
}

@Serializable
internal data class ChannelConfig(val bin: ByteVector) {
    fun export() = fr.acinq.lightning.channel.ChannelConfig(bin.toByteArray())
}

@Serializable
internal data class ChannelType(val bin: ByteVector)

@Serializable
internal data class ChannelFeatures(val bin: ByteVector) {
    fun export() = fr.acinq.lightning.channel.ChannelFeatures(Features(bin.toByteArray()).activated.keys)
}

@Serializable
internal data class ClosingFeerates(val preferred: FeeratePerKw, val min: FeeratePerKw, val max: FeeratePerKw) {
    fun export() = fr.acinq.lightning.channel.states.ClosingFeerates(preferred, min, max)
}

@Serializable
internal data class ClosingTxProposed(val unsignedTx: Transactions.TransactionWithInputInfo.ClosingTx, val localClosingSigned: ClosingSigned) {
    fun export() = fr.acinq.lightning.channel.ClosingTxProposed(unsignedTx, localClosingSigned)
}

@Serializable
internal data class Commitments(
    val channelConfig: ChannelConfig,
    val channelFeatures: ChannelFeatures,
    val localParams: LocalParams,
    val remoteParams: RemoteParams,
    val channelFlags: Byte,
    val localCommit: LocalCommit,
    val remoteCommit: RemoteCommit,
    val localChanges: LocalChanges,
    val remoteChanges: RemoteChanges,
    val localNextHtlcId: Long,
    val remoteNextHtlcId: Long,
    val payments: Map,
    val remoteNextCommitInfo: Either,
    val commitInput: Transactions.InputInfo,
    val remotePerCommitmentSecrets: ShaChain,
    val channelId: ByteVector32,
    val remoteChannelData: EncryptedChannelData = EncryptedChannelData.empty
) {
    fun export() = fr.acinq.lightning.channel.Commitments(
        fr.acinq.lightning.channel.ChannelParams(
            channelId,
            channelConfig.export(),
            channelFeatures.export(),
            localParams.export(),
            remoteParams.export(),
            channelFlags
        ),
        fr.acinq.lightning.channel.CommitmentChanges(
            localChanges.export(),
            remoteChanges.export(),
            localNextHtlcId,
            remoteNextHtlcId,
        ),
        active = listOf(
            fr.acinq.lightning.channel.Commitment(
                fundingTxIndex = 0,
                remoteFundingPubkey = remoteParams.fundingPubKey,
                // We previously didn't store the funding transaction, so we act as if it were unconfirmed.
                // We will put a WatchConfirmed when starting, which will return the confirmed transaction.
                fr.acinq.lightning.channel.LocalFundingStatus.UnconfirmedFundingTx(
                    fr.acinq.lightning.channel.PartiallySignedSharedTransaction(
                        fr.acinq.lightning.channel.SharedTransaction(null, InteractiveTxOutput.Shared(0, commitInput.txOut.publicKeyScript, localCommit.spec.toLocal, localCommit.spec.toRemote, 0.msat), listOf(), listOf(), listOf(), listOf(), 0),
                        // We must correctly set the txId here.
                        TxSignatures(channelId, commitInput.outPoint.txid, listOf()),
                    ),
                    fr.acinq.lightning.channel.InteractiveTxParams(channelId, localParams.isFunder, commitInput.txOut.amount, commitInput.txOut.amount, remoteParams.fundingPubKey, 0, localParams.dustLimit, localCommit.spec.feerate),
                    0
                ),
                fr.acinq.lightning.channel.RemoteFundingStatus.Locked,
                localCommit.export(),
                remoteCommit.export(),
                remoteNextCommitInfo.fold({ x -> fr.acinq.lightning.channel.NextRemoteCommit(x.sent, x.nextRemoteCommit.export()) }, { _ -> null })
            )
        ),
        inactive = emptyList(),
        payments,
        remoteNextCommitInfo.transform({ x -> fr.acinq.lightning.channel.WaitingForRevocation(x.sentAfterLocalCommitIndex) }, { y -> y }),
        remotePerCommitmentSecrets,
        remoteChannelData
    )
}

@Serializable
internal data class OnChainFeerates(val mutualCloseFeerate: FeeratePerKw, val claimMainFeerate: FeeratePerKw, val fastFeerate: FeeratePerKw)

@Serializable
internal data class StaticParams(val chainHash: ByteVector32, val remoteNodeId: PublicKey)

@Serializable
internal sealed class ChannelStateWithCommitments {
    abstract val staticParams: StaticParams
    abstract val currentTip: Pair
    abstract val currentOnChainFeerates: OnChainFeerates
    abstract val commitments: Commitments
    val channelId: ByteVector32 get() = commitments.channelId
    abstract fun export(): PersistedChannelState
}

@Serializable
internal data class WaitForRemotePublishFutureCommitment(
    override val staticParams: StaticParams,
    override val currentTip: Pair,
    override val currentOnChainFeerates: OnChainFeerates,
    override val commitments: Commitments,
    val remoteChannelReestablish: ChannelReestablish
) : ChannelStateWithCommitments() {
    override fun export() =
        WaitForRemotePublishFutureCommitment(commitments.export(), remoteChannelReestablish)
}

/**
 * This class contains data used for channels opened before the migration to dual-funding.
 * We cannot update it or rename it otherwise we would break serialization backwards-compatibility.
 */
@Serializable
internal data class WaitForFundingConfirmed(
    override val staticParams: StaticParams,
    override val currentTip: Pair,
    override val currentOnChainFeerates: OnChainFeerates,
    override val commitments: Commitments,
    val fundingTx: Transaction?,
    val waitingSinceBlock: Long, // how long have we been waiting for the funding tx to confirm
    val deferred: FundingLocked?,
    val lastSent: Either
) : ChannelStateWithCommitments() {
    override fun export() = LegacyWaitForFundingConfirmed(
        commitments.export(),
        fundingTx,
        waitingSinceBlock,
        deferred?.let { ChannelReady(it.channelId, it.nextPerCommitmentPoint) },
        lastSent
    )
}

/**
 * This class contains data used for channels opened before the migration to dual-funding.
 * We cannot update it or rename it otherwise we would break serialization backwards-compatibility.
 */
@Serializable
internal data class WaitForFundingLocked(
    override val staticParams: StaticParams,
    override val currentTip: Pair,
    override val currentOnChainFeerates: OnChainFeerates,
    override val commitments: Commitments,
    val shortChannelId: ShortChannelId,
    val lastSent: FundingLocked
) : ChannelStateWithCommitments() {
    override fun export() = LegacyWaitForFundingLocked(
        commitments.export(),
        shortChannelId,
        ChannelReady(lastSent.channelId, lastSent.nextPerCommitmentPoint)
    )
}

@Serializable
internal data class Normal(
    override val staticParams: StaticParams,
    override val currentTip: Pair,
    override val currentOnChainFeerates: OnChainFeerates,
    override val commitments: Commitments,
    val shortChannelId: ShortChannelId,
    val buried: Boolean,
    val channelAnnouncement: ChannelAnnouncement?,
    val channelUpdate: ChannelUpdate,
    val remoteChannelUpdate: ChannelUpdate?,
    val localShutdown: Shutdown?,
    val remoteShutdown: Shutdown?,
    val closingFeerates: ClosingFeerates?
) : ChannelStateWithCommitments() {
    override fun export() = Normal(
        commitments.export(),
        shortChannelId,
        channelUpdate,
        remoteChannelUpdate,
        localShutdown,
        remoteShutdown,
        closingFeerates?.export(),
        SpliceStatus.None,
        listOf(),
    )
}

@Serializable
internal data class ShuttingDown(
    override val staticParams: StaticParams,
    override val currentTip: Pair,
    override val currentOnChainFeerates: OnChainFeerates,
    override val commitments: Commitments,
    val localShutdown: Shutdown,
    val remoteShutdown: Shutdown,
    val closingFeerates: ClosingFeerates?
) : ChannelStateWithCommitments() {
    override fun export() = ShuttingDown(
        commitments.export(),
        localShutdown,
        remoteShutdown,
        closingFeerates?.export()
    )
}

@Serializable
internal data class Negotiating(
    override val staticParams: StaticParams,
    override val currentTip: Pair,
    override val currentOnChainFeerates: OnChainFeerates,
    override val commitments: Commitments,
    val localShutdown: Shutdown,
    val remoteShutdown: Shutdown,
    val closingTxProposed: List>,
    val bestUnpublishedClosingTx: Transactions.TransactionWithInputInfo.ClosingTx?,
    val closingFeerates: ClosingFeerates?
) : ChannelStateWithCommitments() {
    init {
        require(closingTxProposed.isNotEmpty()) { "there must always be a list for the current negotiation" }
        require(!commitments.localParams.isFunder || !closingTxProposed.any { it.isEmpty() }) { "initiator must have at least one closing signature for every negotiation attempt because it initiates the closing" }
    }

    override fun export() = Negotiating(
        commitments.export(),
        localShutdown,
        remoteShutdown,
        closingTxProposed.map { x -> x.map { it.export() } },
        bestUnpublishedClosingTx,
        closingFeerates?.export()
    )
}

@Serializable
internal data class Closing(
    override val staticParams: StaticParams,
    override val currentTip: Pair,
    override val currentOnChainFeerates: OnChainFeerates,
    override val commitments: Commitments,
    val fundingTx: Transaction?,
    val waitingSinceBlock: Long,
    val mutualCloseProposed: List = emptyList(),
    val mutualClosePublished: List = emptyList(),
    val localCommitPublished: LocalCommitPublished? = null,
    val remoteCommitPublished: RemoteCommitPublished? = null,
    val nextRemoteCommitPublished: RemoteCommitPublished? = null,
    val futureRemoteCommitPublished: RemoteCommitPublished? = null,
    val revokedCommitPublished: List = emptyList()
) : ChannelStateWithCommitments() {
    override fun export() = fr.acinq.lightning.channel.states.Closing(
        commitments.export(),
        waitingSinceBlock,
        mutualCloseProposed,
        mutualClosePublished,
        localCommitPublished?.export(),
        remoteCommitPublished?.export(),
        nextRemoteCommitPublished?.export(),
        futureRemoteCommitPublished?.export(),
        revokedCommitPublished.map { it.export() }
    )
}

@Serializable
internal data class Closed(val state: Closing) : ChannelStateWithCommitments() {
    override val commitments: Commitments get() = state.commitments
    override val staticParams: StaticParams get() = state.staticParams
    override val currentTip: Pair get() = state.currentTip
    override val currentOnChainFeerates: OnChainFeerates get() = state.currentOnChainFeerates
    override fun export() = Closed(state.export())
}

internal object ShaChainSerializer : KSerializer {
    @Serializable
    private data class Surrogate(val knownHashes: List>, val lastIndex: Long? = null)

    override val descriptor: SerialDescriptor = Surrogate.serializer().descriptor

    override fun serialize(encoder: Encoder, value: ShaChain) {
        val surrogate = Surrogate(
            value.knownHashes.map { Pair(it.key.toBinaryString(), it.value.toByteArray()) },
            value.lastIndex
        )
        return encoder.encodeSerializableValue(Surrogate.serializer(), surrogate)
    }

    override fun deserialize(decoder: Decoder): ShaChain {
        val surrogate = decoder.decodeSerializableValue(Surrogate.serializer())
        return ShaChain(surrogate.knownHashes.associate { it.first.toBooleanList() to ByteVector32(it.second) }, surrogate.lastIndex)
    }

    private fun List.toBinaryString(): String = this.map { if (it) '1' else '0' }.joinToString(separator = "")
    private fun String.toBooleanList(): List = this.map { it == '1' }
}

class EitherSerializer(val aSer: KSerializer, val bSer: KSerializer) : KSerializer> {
    @Serializable
    internal data class Surrogate(val isRight: Boolean, val left: A?, val right: B?)

    override val descriptor = Surrogate.serializer(aSer, bSer).descriptor

    override fun serialize(encoder: Encoder, value: Either) {
        val surrogate = Surrogate(value.isRight, value.left, value.right)
        return encoder.encodeSerializableValue(Surrogate.serializer(aSer, bSer), surrogate)
    }

    override fun deserialize(decoder: Decoder): Either {
        val surrogate = decoder.decodeSerializableValue(Surrogate.serializer(aSer, bSer))
        return if (surrogate.isRight) Either.Right(surrogate.right!!) else Either.Left(surrogate.left!!)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy