UVerifyCertificate.java

/*
 * UVerify Backend
 * Copyright (C) 2025 Fabian Bormann
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package io.uverify.backend.model;

import com.bloxbean.cardano.client.plutus.spec.BytesPlutusData;
import com.bloxbean.cardano.client.plutus.spec.ConstrPlutusData;
import com.bloxbean.cardano.client.plutus.spec.ListPlutusData;
import com.bloxbean.cardano.client.plutus.spec.PlutusData;
import com.bloxbean.cardano.client.util.HexUtil;
import io.uverify.backend.dto.CertificateData;
import io.uverify.backend.util.CardanoUtils;
import lombok.*;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UVerifyCertificate {
    private String hash;
    private String algorithm;
    private String issuer;
    private String extra;

    private StateDatum stateDatum;

    public static UVerifyCertificate fromCertificateData(CertificateData certificateData, String address) {
        return UVerifyCertificate.builder()
                .hash(certificateData.getHash())
                .algorithm(certificateData.getAlgorithm())
                .issuer(HexUtil.encodeHexString(CardanoUtils.extractCredentialFromAddress(address)))
                .extra(certificateData.getMetadata())
                .build();
    }

    public static List<UVerifyCertificate> listFromPlutusData(PlutusData plutusData) {
        List<PlutusData> certificates = ((ListPlutusData) plutusData).getPlutusDataList();
        List<UVerifyCertificate> uVerifyCertificates = new ArrayList<>();
        for (PlutusData certificate : certificates) {
            uVerifyCertificates.add(fromPlutusData(certificate));
        }
        return uVerifyCertificates;
    }

    public static UVerifyCertificate fromPlutusData(PlutusData plutusData) {
        UVerifyCertificate uVerifyCertificate = new UVerifyCertificate();
        try {
            List<PlutusData> certificate = ((ConstrPlutusData) plutusData).getData().getPlutusDataList();

            if (certificate.isEmpty()) {
                return null;
            }

            uVerifyCertificate.setHash(HexUtil.encodeHexString(((BytesPlutusData) certificate.get(0)).getValue()));
            uVerifyCertificate.setAlgorithm(new String(((BytesPlutusData) certificate.get(1)).getValue()));
            uVerifyCertificate.setIssuer(HexUtil.encodeHexString(((BytesPlutusData)certificate.get(2)).getValue()));
            ListPlutusData metadataListData = (ListPlutusData) certificate.get(3);
            List<PlutusData> metadataList = metadataListData.getPlutusDataList();
            StringBuilder extra = new StringBuilder();
            for (PlutusData item : metadataList) {
                extra.append(new String(((BytesPlutusData) item).getValue()));
            }
            uVerifyCertificate.setExtra(extra.toString());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return uVerifyCertificate;
    }

    private static List<String> splitStringIntoChunks(String input) {
        byte[] bytes = input.getBytes(StandardCharsets.UTF_8);
        List<String> chunks = new ArrayList<>();

        int start = 0;
        while (start < bytes.length) {
            int end = Math.min(start + 64, bytes.length);

            while (end < bytes.length && (bytes[end] & 0xC0) == 0x80) {
                end--;
            }

            String chunk = new String(bytes, start, end - start, StandardCharsets.UTF_8);
            chunks.add(chunk);
            start = end;
        }

        return chunks;
    }

    private byte[] HashToBytes(String hash) {
        return HexUtil.decodeHexString(hash);
    }

    public PlutusData toPlutusData() {
        ListPlutusData metadataListData = new ListPlutusData();
        if (this.extra != null) {
            if (this.extra.getBytes(StandardCharsets.UTF_8).length > 64) {
                List<String> chunks = splitStringIntoChunks(this.extra);
                for (String chunk : chunks) {
                    metadataListData.add(BytesPlutusData.of(chunk));
                }
            } else {
                metadataListData.add(BytesPlutusData.of(this.extra));
            }
        }

        return ConstrPlutusData.of(0,
                BytesPlutusData.of(HashToBytes(hash)),
                BytesPlutusData.of(algorithm.getBytes()),
                BytesPlutusData.of(HashToBytes(issuer)),
                metadataListData);
    }
}