Skip to content

DVM v9 intrinsics: signature verification, Pedersen commitments, asset balance, point addition (fixes #83) - #84

Closed
liqdmetal wants to merge 4 commits into
DEROFDN:community-devfrom
liqdmetal:feature/dvm-v9-intrinsics
Closed

DVM v9 intrinsics: signature verification, Pedersen commitments, asset balance, point addition (fixes #83)#84
liqdmetal wants to merge 4 commits into
DEROFDN:community-devfrom
liqdmetal:feature/dvm-v9-intrinsics

Conversation

@liqdmetal

@liqdmetal liqdmetal commented Aug 22, 2026

Copy link
Copy Markdown

Summary

Hard-fork proposal: five new DVM-BASIC intrinsics gated to a new DVM version (semver >= 9.0.0), turning DERO smart contracts from "oracle-dependent commitments + ringsize-2-only authorization" into "self-contained confidential settlement." Existing contracts are unaffected — the func_table Range mechanism hides new functions from old DVM versions.

  • verify_sig(pubkey_hex, message, sig_hex) -> Uint64 — Ed25519 in-VM signature verification
  • hash_to_point(input) -> String — deterministic hash-to-curve (protocol generator derivation)
  • pedersen_commit(value, blind_hex) -> String + verify_commit(value, blind_hex, commit_hex) -> Uint64 — Pedersen commitments with 256-bit-hiding blind
  • asset_balance(asset_hex) -> Uint64 — read the SC's own stored balance for any asset
  • ec_add(p1_hex, p2_hex) -> String — homomorphic point addition

Why these, in order

1. verify_sig — the missing half of the K0 fix

The only in-VM authorization primitive today is SIGNER(), which requires ringsize 2 (blockchain/transaction_execute.go) and therefore exposes the sender on-chain. A contract storing a public key and checking verify_sig(pubkey, msg, sig) in SCDATA lets callers prove key ownership anonymously at ringsize >= 4. This is the "Fix C" path for owner-gated contracts (TransferOwnership, UpdateCode, escrow redemption) — authorization without the anonymity cost.

2. hash_to_point + pedersen_commit/verify_commit — oracle-free commitments

The DVM currently expects Pedersen commitments to arrive as external oracles (dvm_functions.go — the comment documents this as "needs more investigation"). These make commitments a first-class primitive: commit on-chain, reveal off-chain, SC verifies — no trust in the caller's commitment. Generator derivation matches the protocol (HashToPoint(HashtoNumber(PROTOCOL_CONSTANT+"H"))), so commitments are compatible with the existing proof system's generators.

3. asset_balance — the stablecoin/settlement primitive

Verified gap: derovalue()/assetvalue() only report the value arriving in the current tx (dvm.State.Assets). The chain persists SC asset balances (LoadSCAssetValue/StoreSCValue), but no intrinsic can read them — a contract can't know its own DERO or asset holding before deciding to pay out. This blocks asset-denominated settlement (stablecoins, tokens) entirely.

4. ec_add — homomorphic accumulation

Pedersen commitments are homomorphic, but DVM-BASIC has no point arithmetic — you can't add two compressed points. ec_add enables updating a stored commitment by a delta without revealing it (the exact pattern for confidential AMM reserves, batched settlement): ec_add(c1, c2) == pedersen_commit(v1+v2, b1+b2), verified by test.

Gas & versioning

Intrinsic ComputeCost Rationale
verify_sig 250,000 2 scalar-mults + hashing (conservative; tune after bench)
hash_to_point 30,000 one G1 scalar-mult
pedersen_commit/verify_commit 45,000 2 scalar-mults + compare
asset_balance 2,000 tree read
ec_add 15,000 one G1 add

All gated semver >= 9.0.0 — a new DVM version; existing contracts see no change.

Tests (dvm/verify_sig_test.go, 6 functions + Fix C wallet half)

  • verify_sig: valid→1, tampered sig/message→0, wrong key→0, malformed→0 (no panic), version gate
  • hash_to_point: determinism, distinct-input separation, valid 33-byte point
  • pedersen_commit/verify_commit: determinism, correct reveal→1, wrong value/blind/commit→0, hiding, binding
  • asset_balance: reads SC's own balance via BalanceLoader(scid, asset), version gate
  • ec_add: homomorphic property (ec_add(c1,c2)==c3), commutativity, valid point, version gate
  • Fix C wallet half: walletapi/sc_auth.go (SCAuthKey helper) + dvm/fixc_auth_test.go — end-to-end owner auth via verify_sig: attacker key rejected, owner authorized, tampered signature rejected

Security notes

  • verify_sig must be non-malleable: contract binds the signed message to the call context (domain || txid || args), never signs bare txids. Secret keys never enter the VM.
  • verify_sig deliberately works on public keys the contract stores — nothing that reveals a caller's key.
  • Commitments only hide if the blind is high-entropy (32-byte CSPRNG); documented as a production rule, not enforced by the VM.
  • Strict point decoding (wargame hardening): all caller-supplied compressed points (ec_add, verify_commit) are validated with strictDecodeG1 — x must be < p (canonical field encoding). Go's lenient DecodeCompressed accepts x ≥ p encodings (computing y from x mod p); a strict decoder (the clean-room Rust port) rejects them. Without the strict check, a contract could feed an encoding one implementation accepts and the other rejects → chain-split class bug. The boundary is pinned in the derohe-rs differential harness (strict_point_decode, 8 vectors). The I3 PR applies the same fix to ec_mul.

Consensus implications

New intrinsics change VM state output → hard fork (DVM version bump to 9.x). Ship as part of the next scheduled HF; the version gate means old contracts keep running byte-identical.

Not included (deliberately)

  • verify_proof (ZK verification in-VM) — a much larger undertaking; the native-hook design is documented but deferred.
  • block_hash, verify_merkle, FOR loops, cross-contract calls — separate proposals.
  • Anti-proposals explicitly out of scope: floats/big-int (determinism), balance_of(address) (leaks other accounts' encrypted balances), WASM/bytecode VM.

Branch: feature/dvm-v9-intrinsics in the fork liqdmetal/derohe-improvements-by-liqdmetal. Carries the build fixes: re-vendored modules (vendor/modules.txt present) and the KickReader removal, so the tree builds from a fresh clone.

…mit, asset_balance, ec_add)

Five new DVM-BASIC intrinsics (gated semver >= 9.0.0, so existing
contracts are unaffected — the func_table Range mechanism):

- verify_sig(pubkey_hex, message, sig_hex) -> Uint64: Ed25519 in-VM
  signature verification. Enables anonymous contract authorization
  (caller proves key ownership in encrypted SCDATA at ringsize >= 4,
  no SIGNER()/ringsize-2) — the K0 Fix C path. Gas 250k. Stdlib, no
  new deps.
- hash_to_point(input) -> String: HashToPoint(HashtoNumber(input)),
  33-byte compressed G1 hex, deterministic across nodes. Pinned to the
  protocol generator derivation (algebra_pedersen.go). Gas 30k.
- pedersen_commit(value, blind_hex) -> String + verify_commit(value,
  blind_hex, commit_hex) -> Uint64: v*G + r*H with NUMS H (hash-to-point
  of PROTOCOL_CONSTANT+H), 32-byte blind (256-bit hiding). Commit
  on-chain, reveal off-chain, SC verifies — no oracle trust. Gas 45k.
- asset_balance(asset_hex) -> Uint64: reads the SC's OWN stored balance
  for any asset (incl. DERO) via BalanceLoader(scid, asset). Closes the
  gap where derovalue()/assetvalue() only report the current tx's
  incoming value. Gas 2k.
- ec_add(p1_hex, p2_hex) -> String: homomorphic accumulation of
  compressed G1 points — ec_add(c1,c2) == pedersen_commit(v1+v2, b1+b2),
  enabling commitment-state updates (e.g. AMM reserves) without
  revealing deltas. Gas 15k.

Tests (dvm/verify_sig_test.go): 6 test functions covering valid/tampered/
malformed inputs, determinism, version gate, the homomorphic property,
and BalanceLoader wiring.

Consensus note: new intrinsics change VM state output -> hard fork
(DVM version bump). The version gate means old contracts keep running
unchanged. This is the foundation for K0 Fix C and confidential
settlement.

Also carries the build-manifest fix (go.mod/go.sum): the current tree
does not build from a fresh clone.
@liqdmetal

Copy link
Copy Markdown
Author

Related PRs in this K0/DVM series

These are part of one coordinated package, deliberately split into independent, reviewable threads:

PR What Consensus Role
#80 Fix A — wallet ringsize-2 warning + PrivacyWarning RPC none (ships now) the non-consensus wallet/API half
#82 Fix B1 — consensus min-ring-4 floor for NORMAL/BURN hard fork the consensus enforcement
#84 DVM v9verify_sig, hash_to_point, pedersen_commit/verify_commit, asset_balance, ec_add hard fork (VM surface) the foundation for Fix C (anonymous owner auth at ringsize ≥ 4, replacing the last ringsize-2 need)

Ordering logic:

@liqdmetal
liqdmetal force-pushed the feature/dvm-v9-intrinsics branch from 61e3da3 to 95f4e30 Compare August 23, 2026 03:26
liqdmetal added a commit to liqdmetal/derohe-improvements-by-liqdmetal that referenced this pull request Aug 23, 2026
…T auto-detect)

The consensus half of closing the ringsize-2 loophole for SC calls: a
ringsize-2 SC_TX exposes the signer by design (the ring IS sender+receiver,
parity selects the sender). This makes ringsize 2 structurally impossible
for contracts that don't need it.

Design (k0-fix-design.md Fix B2):
- SC_META_DATA gains a NoSigner bit (high bit of the Type byte). The 33-byte
  wire format is UNCHANGED, so existing metadata stays valid and existing
  contracts default to uses_signer=true (preserving behavior).
- At install (transaction_execute.go), the parsed contract AST is scanned
  for SIGNER() calls (dvm.ContractUsesSigner). No SIGNER() -> NoSigner bit
  set. Contract authors need no changes.
- Consensus enforcement (transaction_verify.go): SC_TX + ringsize 2 +
  NoSigner-marked contract -> rejected with a clear error. Contracts that
  genuinely call SIGNER() keep ringsize 2 (owner-gated entrypoints) until
  the verify_sig migration (K0 Fix C, DVM v9 PR DEROFDN#84) removes that need.

Tests (dvm/k0_b2_test.go):
- TestContractUsesSigner: detects SIGNER(), no false positive
- TestSCMetaNoSignerBit: bit set/clear, 33-byte round-trip, private+NoSigner
  coexistence

Composes with the K0 package: Fix A (DEROFDN#80) warns, Fix B1 (DEROFDN#82) bans
ringsize-2 NORMAL/BURN, Fix B2 bans ringsize-2 SC_TX for NoSigner
contracts, Fix C (DEROFDN#84 verify_sig) removes the last legitimate ringsize-2
need.

Also carries the build-manifest fix (go.mod/go.sum).
Regenerates vendor/ via go mod tidy + go mod vendor (adds missing
vendor/modules.txt) so the tree builds from a fresh clone without
-mod=mod. Deps unchanged.
community-dev base calls l.Operation.KickReader(), which no published
chzyer/readline implements (v1.5.1) -> wallet-cli fails to compile from a
fresh clone. Replace with the same UI shim used on the main fork branch:
_ = l.Operation (read-unblock helper, wallet-only, not consensus).
Same chain-split class fix as the I3 PR: caller-supplied compressed points
are decoded via strictDecodeG1 (x < p validation) so off-curve x>=p
encodings are rejected identically across implementations (Go + the
clean-room Rust port). verify_commit returns 0, ec_add panics (recovered
-> deterministic failure). Canonical points unaffected; full dvm suite
green.
@liqdmetal

Copy link
Copy Markdown
Author

Superseded by PR #128 — the consolidated intrinsics package. Same code, one reviewable PR with no vendor noise.

@liqdmetal liqdmetal closed this Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant