Skip to content

CONFIDENTIAL TRANSACTIONS - #5

Open
aivve wants to merge 110 commits into
masterfrom
dev/ct
Open

CONFIDENTIAL TRANSACTIONS#5
aivve wants to merge 110 commits into
masterfrom
dev/ct

Conversation

@aivve

@aivve aivve commented May 13, 2026

Copy link
Copy Markdown
Member

Confidential Transactions for Karbo

This PR introduces an initial implementation of Confidential Transactions for Karbo using Pedersen commitments and GK-style denomination proofs.

KarboCT is deliberately a practical middle ground: better trading and transfer privacy than transparent CryptoNote amounts, with simpler math and implementation scope than arbitrary-amount Bulletproof-style CT.

Instead of using full range proofs such as Bulletproofs, Karbo CT uses GK-style denomination proofs over an allowed denomination set. Each output commits to one of 64 canonical denominations using a Pedersen commitment proved by a Groth-Kohlweiss one-out-of-many membership proof. The 64-entry denomination set is d × 10^k for d ∈ {1..9}, k ∈ {10..16}, plus the cap entry 10^17.

MIN_CT_DENOMINATION = 10^10 au = 0.01 KRB

Sub-floor pieces (less than 10^10 au) cannot become CT outputs and are absorbed into transaction fees at construction time.This makes confidential dust structurally impossible: any value below the floor either remains in atransparent pre-fork output (until consumed) or folds into a fee.

Coinbase remains transparent.

This PR follows paper where you can find more information.

aivve added 10 commits May 11, 2026 20:48
CT blinding factor domain separation - derive_blinding_factor now uses domain tag "ct-blinding-v1" in its hash input, breaking the collision with derivation_to_scalar used by stealth addresses
Removed duplicate declaration checkConfidentialTransaction
Initialized TransactionPrefix::fee
Replace ringAmount: uint64 + ringOutputIndexes: vec<uint32> with ringMembers: vec<{amount: uint64, offset: uint32}>

allows to freely mix:

Transparent decoys of any amount with a CT real spend
CT decoys with a transparent real spend (pre-fork legacy)
Transparent + CT decoys all together
The validator now uses batched verify for every CT transaction, with the diagnostic per-output fallback only firing on rejection
aivve added 18 commits May 12, 2026 23:31
Add a consensus-tracked confidential_supply field that records the total
visible value currently locked inside the ECC CT pool, alongside a
pq_plain_supply stub for the future PQ-plain output type. Both are stored
in DbBlockMeta and BlockEntry the same way already_generated_coins is, so
they survive reorgs, pop, and migration.

Per non-coinbase tx the delta is
  delta = visible_plain_inputs - visible_plain_outputs - fee
which is 0 for plain->plain, positive when value enters the CT pool, and
negative when it exits to plain or PQ-plain. The invariant
  visible_plain_supply + pq_plain_supply + confidential_supply == already_generated_coins
is preserved across all chain operations.

Underflow is rejected at three layers:
  - mempool admission via checkConfidentialTransaction (single-tx outflow > tip pool)
  - block template construction via tx_memory_pool::fill_block_template
    (running pool simulation catches chained exits)
  - block push (final authoritative accumulation)

Exposed via ICore::getConfidentialSupply / getPqPlainSupply and per-block
*AtBlock(hash, &out) accessors, surfaced in RPC /getinfo as
confidential_supply and pq_plain_supply (formatted, like
already_generated_coins) and in BlockchainExplorer block details.

Block v7 is reserved: BLOCK_MAJOR_VERSION_7, UPGRADE_HEIGHT_V7,
m_upgradeHeightV7 + builder, m_upgradeDetectorV7 plus its
init/checkAndRollback/blockPushed/blockPopped wiring, PoW switch and
serialization upper-bound bump. Not activated yet; intended for the
future PQ-plain output type.
…-out

The v6+ "only CT transactions allowed" block-validation rule, the
"legacy rejected in CT-only era" branch in Core::check_tx_syntax, and
the strict version-equals-current check in
TransactionPool::isTxVersionAllowedForHeight are all relaxed so that
both legacy v1 plain and v2 CT transactions remain valid after the CT
fork. Wallets default to CT, but v1 plain stays available for tooling,
exchanges, and explicit opt-out users.

Pre-fork unchanged: only v1 is accepted. Post-fork: both v1 and v2.
TRANSACTION_VERSION_CT is still rejected before CT activation height,
and unknown versions are still rejected outright. The transparent-output
floor (>= MIN_CT_DENOMINATION post-fork) is unchanged; v1 senders must
still use canonical decompositions.

CT pool liability accounting is unaffected: v1 plain->plain has
delta = 0 in the same formula used for CT-mixing cases.

simplewallet gains a --legacy-tx startup flag that opts the wallet out
of CT and sends v1 plain transactions even when the chain is post-fork.
The flag flows through WalletLegacy::setForceLegacyTxs and is captured
by WalletTransactionSender when it's constructed (each of the seven
WalletLegacy construction sites in simplewallet now applies the flag,
including the wallet-rpc-server entry point in main()).
WalletGreen gets setForceLegacyTxs(bool) / forceLegacyTxs() mirroring
WalletLegacy. The single useCT decision in WalletGreen::makeTransaction
is gated on !m_forceLegacyTxs so the wallet sends v1 plain transactions
even when the chain is post-fork.

GreenWallet exposes --legacy-tx via Config::legacyTx (Types.h),
parseArguments wires it in, and ZedWallet applies it after constructing
the WalletGreen instance.

walletd exposes the same as a --legacy-tx boost program option in
PaymentServiceConfiguration; PaymentGateService::runWalletService calls
setForceLegacyTxs on the WalletGreen it constructs. Generation paths
in PaymentGate/WalletService.cpp don't send transactions, so they're
left untouched.
Block major v6 replaces the legacy dual height/timestamp interpretation
of TransactionPrefix::unlockTime with a height-only rule capped at
CRYPTONOTE_MAX_UNLOCK_HEIGHT_V6 (10M blocks, ~76 years at 240s/block):
- New txs at v6+ tip are rejected if unlockTime exceeds the cap. CT
  already requires unlockTime == 0; this also constrains v1 plain txs
  that remain valid post-fork.
- When a v6+ tx tries to spend an output whose source tx carries an
  unlockTime above the cap, the output is treated as unlocked. This
  recovers funds frozen by accidental Unix-timestamp values set under
  the legacy rule (no escrow/vesting usage exists in practice).
- Pre-v6 spend evaluations keep the old behavior unchanged.
TransfersContainer mirrors the consensus rule so wallets display
recovered outputs as spendable. The two-arg
Blockchain::is_tx_spendtime_unlocked(unlock_time, height) overload
applies the v6 rule when height >= UPGRADE_HEIGHT_V6, so the
spending-proof RPC path picks it up automatically.
- Allow v1 plain alongside v2 CT txs after the fork; --legacy-tx and pre-CT wallets keep working with full CN decomposition.
- Allow KeyInput / ConfidentialInput mix in v2 (per-input dispatch, balance kernel handles both). Wallet routes transparent dust as KeyInput so the CT pool tracks shielding flows.
- Triptych spend proofs replace MLSAG: GK-descended logarithmic linkable one-out-of-many, with a Schnorr branch at ring size 1 and batched verify via shared Pippenger MSM.
- CTInputSignature gains n=0xFF empty-slot sentinel for v2 KeyInput slots; legacy ring sig lives in tx.signatures[i] for those slots.
- Pre-CT wire compat for getrandom_outs via parallel outs / outs_extra arrays so old wallets keep deserializing the response.
- Fix KV-binary RPC roundtrip for ConfidentialInput by wrapping ring members as objects (same fix in explorer details serializer).
- Filter zero-bucket entries from CT mixing-decoy requests.
- Built-in explorer: unified per-input Signatures section, input/output counts, "dust" wording, diagnostic queryBlocksLite log.
- Round-trip self-tests for v2 mixed inputs (binary stream + KV binary).
# Conflicts:
#	src/Rpc/BuiltinExplorer.cpp
#	src/Rpc/BuiltinExplorer.h
isTransparentNonCanonicalCtAmount only flagged sub-cent dust
(amount % MIN_CT_DENOMINATION != 0). 0.01-aligned outputs that are
not a valid CN {1,2,5}*10^k denomination — most notably v5+
coinbase rewards (~2.92 KRB each, the emission decrement) — landed
in unusedUnmixable and were only spendable with mixin = 0
(dust_sweep). With Phase B those outputs are perfectly spendable as
v2 KeyInputs (coinbase uses ring size 1, no decoys needed), but the
selection bucket still gated them out at normal mixin.

Widen the predicate to also flag 0.01-aligned non-CN-canonical
outputs as Phase 2 candidates. They now flow through
nonCanonicalOutputs, chooseInputMixins routes coinbase to ring 1
via the existing carve-out, and a mining wallet can finally send
its accumulated rewards without --legacy-tx + mixin 0.

Same fix in both WalletLegacy and WalletGreen helpers.
Under Phase B every transparent output is spendable via v2 KeyInput
(coinbase uses ring size 1, others fall back gracefully). The
"unmixable" balance line and the "you have unmixable coins — set
mixin to zero" prompts were left over from the v1 ring-only era
and now lie to the user about what they can spend.

Drop user-facing references:
  - simplewallet `balance` no longer lists unmixable.
  - simplewallet `transfer` no longer prints the unmixable-coins
    warning at send time.
  - simplewallet wallet-open log line drops the unmixable field.
  - greenwallet `transfer` --send-all path just sends
    `balance - fee - nodeFee`; the dust carve-out and mixin-zero
    confirmation prompt are gone.
  - greenwallet `doWeHaveEnoughBalance` keeps the
    "not enough funds" branch and drops the unmixable branch.

The underlying unmixableBalance() and getBalanceMinusDust() APIs
stay on IWalletLegacy / WalletGreen — no callers outside the UI
removed here, and they remain useful for diagnostics.
ctSignatures[i] with n=0 used to render as 'ring size 1'. n=0 is no
longer a valid proof shape after consensus dropped the Schnorr branch,
so the (n == 0) ? 1 : (1 << n) special case can collapse to plain
1 << n for the valid n in {2, 3, 4}. Empty-slot (n=0xFF) is still
handled separately as 'transparent shielding'.
Most v2 inputs in mining-heavy wallets are KeyInput shieldings of
coinbase outputs (cheap legacy ring-1 sigs, microseconds to verify).
Triptych ConfidentialInputs are single-digit ms even batched, so the
worst-case all-ring-16 shape is still under ~2 s/tx. 128 was tight
for batch coinbase consolidation; 512 lets a miner shield ~4x more
mature blocks per transaction. CT_MAX_OUTPUTS stays at 64 — GK output
proofs are the real asymmetric verifier cost.
Split the home-page header line in two. Row 1 keeps tip metrics
(Height, Difficulty, Alt. blocks, Transactions) and gains a new
Account numbers count. Row 2 collects supply metrics (Emission,
Next reward) and adds Confidential supply (formatted via the
existing amount formatter), so the CT pool is visible right next
to total emission.
aivve added 30 commits May 29, 2026 22:53
(cherry picked from commit 96a800b8da7d94b5e91018464dff8165da7d9979)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant