// Copyright (c) 2017-2026 Lethean (https://lt.hn) // // Licensed under the European Union Public Licence (EUPL) version 1.2. // SPDX-License-Identifier: EUPL-1.2 package consensus import ( "bytes" "fmt" coreerr "dappco.re/go/core/log" "dappco.re/go/core/blockchain/types" "dappco.re/go/core/blockchain/wire" ) // zcSigData holds the parsed components of a ZC_sig variant element // needed for CLSAG GGX verification. type zcSigData struct { pseudoOutCommitment [32]byte // premultiplied by 1/8 on chain pseudoOutAssetID [32]byte // premultiplied by 1/8 on chain clsagFlatSig []byte // flat: c(32) | r_g[N*32] | r_x[N*32] | K1(32) | K2(32) ringSize int } // v2SigEntry is one parsed entry from the V2 signature variant vector. type v2SigEntry struct { tag uint8 zcSig *zcSigData // non-nil when tag == SigTypeZC } // parseV2Signatures parses the SignaturesRaw variant vector into a slice // of v2SigEntry. The order matches the transaction inputs. func parseV2Signatures(raw []byte) ([]v2SigEntry, error) { if len(raw) == 0 { return nil, nil } dec := wire.NewDecoder(bytes.NewReader(raw)) count := dec.ReadVarint() if dec.Err() != nil { return nil, coreerr.E("parseV2Signatures", "read sig count", dec.Err()) } entries := make([]v2SigEntry, 0, count) for i := uint64(0); i < count; i++ { tag := dec.ReadUint8() if dec.Err() != nil { return nil, coreerr.E("parseV2Signatures", fmt.Sprintf("read sig tag %d", i), dec.Err()) } entry := v2SigEntry{tag: tag} switch tag { case types.SigTypeZC: zc, err := parseZCSig(dec) if err != nil { return nil, coreerr.E("parseV2Signatures", fmt.Sprintf("parse ZC_sig %d", i), err) } entry.zcSig = zc case types.SigTypeVoid: // Empty struct — nothing to read. case types.SigTypeNLSAG: // Skip: varint(count) + count * 64-byte signatures. n := dec.ReadVarint() if n > 0 && dec.Err() == nil { _ = dec.ReadBytes(int(n) * 64) } case types.SigTypeZarcanum: // Skip: 10 scalars + bppe + public_key + CLSAG_GGXXG. // Use skipZarcanumSig to advance past the data. skipZarcanumSig(dec) default: return nil, coreerr.E("parseV2Signatures", fmt.Sprintf("unsupported sig tag 0x%02x", tag), nil) } if dec.Err() != nil { return nil, coreerr.E("parseV2Signatures", fmt.Sprintf("parse sig %d (tag 0x%02x)", i, tag), dec.Err()) } entries = append(entries, entry) } return entries, nil } // parseZCSig parses a ZC_sig element (after the tag byte) from the decoder. // Wire: pseudo_out_amount_commitment(32) + pseudo_out_blinded_asset_id(32) + CLSAG_GGX_serialized. func parseZCSig(dec *wire.Decoder) (*zcSigData, error) { var zc zcSigData dec.ReadBlob32(&zc.pseudoOutCommitment) dec.ReadBlob32(&zc.pseudoOutAssetID) if dec.Err() != nil { return nil, dec.Err() } // CLSAG_GGX_serialized wire format: // c(32) + varint(N) + r_g[N*32] + varint(N) + r_x[N*32] + K1(32) + K2(32) // // C bridge expects flat: // c(32) | r_g[N*32] | r_x[N*32] | K1(32) | K2(32) var c [32]byte dec.ReadBlob32(&c) rgCount := dec.ReadVarint() rgBytes := dec.ReadBytes(int(rgCount) * 32) rxCount := dec.ReadVarint() rxBytes := dec.ReadBytes(int(rxCount) * 32) if dec.Err() != nil { return nil, dec.Err() } if rgCount != rxCount { return nil, coreerr.E("parseZCSig", fmt.Sprintf("CLSAG r_g count %d != r_x count %d", rgCount, rxCount), nil) } zc.ringSize = int(rgCount) var k1, k2 [32]byte dec.ReadBlob32(&k1) dec.ReadBlob32(&k2) if dec.Err() != nil { return nil, dec.Err() } // Build flat sig for C bridge. flat := make([]byte, 0, 32+len(rgBytes)+len(rxBytes)+64) flat = append(flat, c[:]...) flat = append(flat, rgBytes...) flat = append(flat, rxBytes...) flat = append(flat, k1[:]...) flat = append(flat, k2[:]...) zc.clsagFlatSig = flat return &zc, nil } // skipZarcanumSig advances the decoder past a zarcanum_sig element. // Wire: 10 scalars + bppe_serialized + public_key(32) + CLSAG_GGXXG. func skipZarcanumSig(dec *wire.Decoder) { // 10 fixed scalars/points (320 bytes). _ = dec.ReadBytes(10 * 32) // bppe_serialized: vec(L) + vec(R) + 7 scalars (224 bytes). skipVecOfPoints(dec) // L skipVecOfPoints(dec) // R _ = dec.ReadBytes(7 * 32) // pseudo_out_amount_commitment (32 bytes). _ = dec.ReadBytes(32) // CLSAG_GGXXG: c(32) + vec(r_g) + vec(r_x) + K1(32) + K2(32) + K3(32) + K4(32). _ = dec.ReadBytes(32) // c skipVecOfPoints(dec) // r_g skipVecOfPoints(dec) // r_x _ = dec.ReadBytes(128) // K1+K2+K3+K4 } // skipVecOfPoints advances the decoder past a varint(count) + count*32 vector. func skipVecOfPoints(dec *wire.Decoder) { n := dec.ReadVarint() if n > 0 && dec.Err() == nil { _ = dec.ReadBytes(int(n) * 32) } } // v2ProofData holds parsed proof components from the Proofs raw bytes. type v2ProofData struct { // bgeProofs contains one BGE proof blob per output (wire-serialised). // Each blob can be passed directly to crypto.VerifyBGE. bgeProofs [][]byte // bppProofBytes is the bpp_signature blob (wire-serialised). // Can be passed directly to crypto.VerifyBPP. bppProofBytes []byte // bppCommitments are the amount_commitments_for_rp_aggregation (E'_j). // Premultiplied by 1/8 as stored on chain. bppCommitments [][32]byte // balanceProof is the generic_double_schnorr_sig (96 bytes: c, y0, y1). balanceProof []byte } // parseV2Proofs parses the Proofs raw variant vector. func parseV2Proofs(raw []byte) (*v2ProofData, error) { if len(raw) == 0 { return &v2ProofData{}, nil } dec := wire.NewDecoder(bytes.NewReader(raw)) count := dec.ReadVarint() if dec.Err() != nil { return nil, coreerr.E("parseV2Proofs", "read proof count", dec.Err()) } var data v2ProofData for i := uint64(0); i < count; i++ { tag := dec.ReadUint8() if dec.Err() != nil { return nil, coreerr.E("parseV2Proofs", fmt.Sprintf("read proof tag %d", i), dec.Err()) } switch tag { case 46: // zc_asset_surjection_proof: varint(nBGE) + nBGE * BGE_proof nBGE := dec.ReadVarint() if dec.Err() != nil { return nil, coreerr.E("parseV2Proofs", "parse BGE count", dec.Err()) } data.bgeProofs = make([][]byte, nBGE) for j := uint64(0); j < nBGE; j++ { data.bgeProofs[j] = readBGEProofBytes(dec) if dec.Err() != nil { return nil, coreerr.E("parseV2Proofs", fmt.Sprintf("parse BGE proof %d", j), dec.Err()) } } case 47: // zc_outs_range_proof: bpp_serialized + aggregation_proof data.bppProofBytes = readBPPBytes(dec) if dec.Err() != nil { return nil, coreerr.E("parseV2Proofs", "parse BPP proof", dec.Err()) } data.bppCommitments = readAggregationCommitments(dec) if dec.Err() != nil { return nil, coreerr.E("parseV2Proofs", "parse aggregation proof", dec.Err()) } case 48: // zc_balance_proof: 96 bytes (c, y0, y1) data.balanceProof = dec.ReadBytes(96) if dec.Err() != nil { return nil, coreerr.E("parseV2Proofs", "parse balance proof", dec.Err()) } default: return nil, coreerr.E("parseV2Proofs", fmt.Sprintf("unsupported proof tag 0x%02x", tag), nil) } } return &data, nil } // readBGEProofBytes reads a BGE_proof_s and returns the raw wire bytes. // Wire: A(32) + B(32) + vec(Pk) + vec(f) + y(32) + z(32). func readBGEProofBytes(dec *wire.Decoder) []byte { var raw []byte // A + B ab := dec.ReadBytes(64) raw = append(raw, ab...) // Pk vector pkCount := dec.ReadVarint() if dec.Err() != nil { return nil } raw = append(raw, wire.EncodeVarint(pkCount)...) if pkCount > 0 { pkData := dec.ReadBytes(int(pkCount) * 32) raw = append(raw, pkData...) } // f vector fCount := dec.ReadVarint() if dec.Err() != nil { return nil } raw = append(raw, wire.EncodeVarint(fCount)...) if fCount > 0 { fData := dec.ReadBytes(int(fCount) * 32) raw = append(raw, fData...) } // y + z yz := dec.ReadBytes(64) raw = append(raw, yz...) return raw } // readBPPBytes reads a bpp_signature_serialized and returns the raw wire bytes. // Wire: vec(L) + vec(R) + A0(32) + A(32) + B(32) + r(32) + s(32) + delta(32). func readBPPBytes(dec *wire.Decoder) []byte { var raw []byte // L vector lCount := dec.ReadVarint() if dec.Err() != nil { return nil } raw = append(raw, wire.EncodeVarint(lCount)...) if lCount > 0 { raw = append(raw, dec.ReadBytes(int(lCount)*32)...) } // R vector rCount := dec.ReadVarint() if dec.Err() != nil { return nil } raw = append(raw, wire.EncodeVarint(rCount)...) if rCount > 0 { raw = append(raw, dec.ReadBytes(int(rCount)*32)...) } // 6 fixed scalars raw = append(raw, dec.ReadBytes(6*32)...) return raw } // readAggregationCommitments reads the aggregation proof and extracts // the amount_commitments_for_rp_aggregation (the first vector). // Wire: vec(commitments) + vec(y0s) + vec(y1s) + c(32). func readAggregationCommitments(dec *wire.Decoder) [][32]byte { // Read commitments vector. nCommit := dec.ReadVarint() if dec.Err() != nil { return nil } commits := make([][32]byte, nCommit) for i := uint64(0); i < nCommit; i++ { dec.ReadBlob32(&commits[i]) } // Skip y0s vector. skipVecOfPoints(dec) // Skip y1s vector. skipVecOfPoints(dec) // Skip c scalar. _ = dec.ReadBytes(32) return commits }