Update go.mod module line, all require/replace directives, and every .go import path from forge.lthn.ai/core/go-blockchain to dappco.re/go/core/blockchain. Add replace directives to bridge dappco.re paths to existing forge.lthn.ai registry during migration. Update CLAUDE.md, README, and docs to reflect the new module path. Co-Authored-By: Virgil <virgil@lethean.io>
340 lines
9 KiB
Go
340 lines
9 KiB
Go
// 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
|
|
}
|