Skip to content

DVM intrinsics: verification + group-arithmetic primitives (verify_sig, commitments, ec_add, ec_mul, verify_adaptor) (fixes #127) - #128

Open
liqdmetal wants to merge 3 commits into
DEROFDN:community-devfrom
liqdmetal:feat/dvm-intrinsics
Open

liqdmetal wants to merge 3 commits into
DEROFDN:community-devfrom
liqdmetal:feat/dvm-intrinsics

Conversation

@liqdmetal

@liqdmetal liqdmetal commented Aug 25, 2026

Copy link
Copy Markdown

#128 — DVM v9/v10 crypto intrinsics: budget + chain-activation gate

Branch: feat/dvm-intrinsics (+ 2 follow-on commits on feat/dvm-crypto-budget)
Target: DEROFDN/derohe community-dev

This PR adds the confidential-settlement and signature-authorization primitives to DVM-BASIC, plus two safety mechanisms that must land with them: a weighted CPU budget and a chain hard-fork activation gate.

Added opcodes

Opcode Gate Compute gas Crypto weight
verify_sig(pub, msg, sig) — Ed25519 >=9.0.0 250,000 5
hash_to_point(x) — bn256 HashToPoint >=9.0.0 30,000 3
ec_add(P, Q) >=9.0.0 15,000 9
ec_mul(P, n) >=9.0.0 30,000 12
pedersen_commit(v, r) >=9.0.0 45,000 16
verify_commit(v, r, C) >=9.0.0 45,000 20
asset_balance(asset) >=9.0.0 2,000 0
verify_adaptor(P, m, σ') — true Schnorr adaptor >=10.0.0 250,000 29

The "crypto weight" is a new meter (see below); compute gas is unchanged from the original intrinsics PR.

1. Weighted crypto budget (dvm/crypto_budget.go)

Why a second meter. Compute gas prices opcodes as if they were all cheap hashes: a 64-byte sha256 is 25,000 gas for ~0.1 µs, but the EC opcodes are 15,000–250,000 gas for 83–285 µs. Measured through the real in-VM entry points on a Ryzen 9 5900X that is 235,849 gas/µs for sha256 against 211–5,082 gas/µs for the EC ops. Under the flat 10,000,000 compute cap (dvm/sc.go:238) a program can therefore buy ~30–47 ms of CPU where the previous worst case was ~42 µs. No single gas price fixes both ends — matching sha256 would need ec_mul at ~15,000,000 gas, more than the whole cap.

The fix. A deterministic, cost-weighted meter on the expensive class, charged at the existing dispatch site (Handle_Internal_Function) so an opcode can't be added without either a weight or an explicit zero:

  • Weights are ceil(measured µs / 10 µs), measured in-VM, so CRYPTO_BUDGET_UNITS = 100 caps worst-case CPU at ~1 ms for any opcode mix (a per-opcode cap would miss mixed programs).
  • The meter lives on Shared_State, which every DVM shares, so nested/cross-contract calls can't refresh it by re-entering the VM.
  • Exhaustion panics and is recovered by RunSmartContract into an execution error — a revert, not a node abort.
  • Compute gas is unchanged; the budget is an additional bound.

2. Chain-activation gate (ChainVersionFromHardFork, dvm/dvm.go, dvm/sc.go, dvm/dvm_functions.go, blockchain/transaction_execute.go, dvm/simulator.go)

The bug this fixes (security). hard_fork_version_current was a dead parameter: Execute_sc_function and Simulator.common accepted it but never read it, transaction_execute.go hardcoded 1, and the only writer of dvm.Version was the contract's own VERSION() call. The >=9.0.0 opcode gate was therefore author-declared, not chain-activated — any contract could self-activate these intrinsics the moment the code shipped.

The fix. Shared_State gains ChainVersion; Execute_sc_function sets it from the real chain version; Handle_Internal_Function requires an opcode's Range to be satisfied by both the contract's declared version and the chain version. transaction_execute.go now passes chain.Get_Current_Version_at_Height. The simulator gets a version field (default 1) so tests can choose their fork.

Activation mapping (proposed; adjust to whichever fork ships these):

chain HF DVM feature
1–3 (current mainnet) 8.0.0 — intrinsics off
4 9.0.0
5 10.0.0

So today a contract declaring 9.0.0 gets verify_sig rejected; the intrinsics only go live at a real hard fork. Cheap opcodes (>=0.0.0) are unaffected.

Tests

  • Table weights + that cheap opcodes stay at zero (TestCryptoBudgetTableWeights)
  • Exact exhaustion boundary — 8 ec_mul pass, 9th fails (TestCryptoBudgetBoundary)
  • Mixed 103-unit program a per-opcode cap would miss (TestCryptoBudgetIsCombinationProof)
  • Budget persists across re-entrant calls (TestCryptoBudgetPersistsAcrossNestedCalls)
  • End-to-end DVM-BASIC program: 8 EC_MUL revert-free, 9 reverted (TestCryptoBudgetEndToEndProgram)
  • hf→feature mapping (TestChainVersionFromHardFork)
  • Dispatch-level AND-gate; sha256 unaffected (TestChainVersionGatesIntrinsics)
  • v9 program fails on hf 3, runs on hf 4 (TestChainVersionGatesEndToEnd)
  • Fix C wallet-signed auth runs the simulator at hf 4 (TestFixC_WalletSignedAuthorization)
  • Re-measurable via BenchmarkCryptoIntrinsic*

Consensus impact

  • New opcodes = new consensus semantics. Gated at >=9.0.0/>=10.0.0, and now additionally gated behind the chain hard fork.
  • Weights and CRYPTO_BUDGET_UNITS are consensus parameterscrypto_budget.go documents the measurement basis and that they must be re-measured before any change.
  • No change to existing opcode gas prices or any non-intrinsic rule.

Commits

  1. bd3f33f feat(dvm): weighted crypto budget for the expensive v9/v10 intrinsics
  2. f775789 feat(dvm): gate the v9/v10 intrinsics on the chain hard-fork version

Rebuilt on DEROFDN community-dev (post-DEROFDN#129 func_table refactor).

DVM v9 intrinsics gated >=9.0.0: verify_sig, hash_to_point,
pedersen_commit, verify_commit, asset_balance, ec_add, ec_mul.
K0 Fix C SC-auth (walletapi/sc_auth.go). verify_adaptor (>=10.0.0) is a
TRUE Schnorr adaptor: blob s'(64)||R(33)||T(33)=130B, s'*G == R-T+e*P,
revealing t completes a Schnorr signature. Strict x<p decode + low-s.

Tests: dvm full suite green; walletapi sc_auth green.
@liqdmetal
liqdmetal force-pushed the feat/dvm-intrinsics branch from aa6a2b3 to 6d443d8 Compare August 31, 2026 12:08
The v9 EC/signature intrinsics cost 15,000-250,000 compute gas for 83-285 us
of real work, while the incumbent schedule prices a 64-byte sha256 at 25,000
gas (~0.1 us). Measured through the real in-VM entry points that is
235,849 gas/us for sha256 against 211-5,082 gas/us for the EC ops, so under the
flat 10,000,000 compute cap a program can buy ~30-47 ms of CPU where the
previous worst case was ~42 us. No single gas price fixes both ends: pricing
ec_mul to match sha256 needs ~15,000,000 gas, more than the whole cap.

Add a second, deterministic meter for the expensive class:

  hash_to_point 25.9us ->  3 units     pedersen_commit 159.3us -> 16 units
  verify_sig    49.7us ->  5 units     verify_commit   197.6us -> 20 units
  ec_add        83.3us ->  9 units     verify_adaptor  285.1us -> 29 units
  ec_mul       112.5us -> 12 units     (1 unit = 10 us, ceilings)

CRYPTO_BUDGET_UNITS = 100 caps total crypto work per SC execution at ~1 ms
worst case for ANY opcode mix, because weights are ceilings. The weights ride
in the intrinsic table (func_data.CryptoCost) and are charged at the existing
dispatch site, so an opcode cannot be added without either being weighted or
explicitly defaulting to zero. The meter lives on Shared_State, which every DVM
triggered by the first call shares, so nested/cross-contract calls cannot
refresh the budget by re-entering the VM. Budget exhaustion panics and is
recovered by RunSmartContract into an execution error, like the gas meters, so
an over-budget call reverts rather than aborting the node.

Compute gas is unchanged; the budget is an additional bound.

Tests: table weights (and that cheap opcodes stay at zero), the exact
exhaustion boundary (8 ec_mul pass, 9th fails), a valid 103-unit mixed program
that a per-opcode cap would miss, budget persistence across re-entrant calls,
an end-to-end DVM-BASIC program (8 EC_MUL revert-free, 9 reverted), plus
BenchmarkCryptoIntrinsic* to re-measure the weights before any change.
hard_fork_version_current was a dead parameter: Execute_sc_function and
Simulator.common accepted it but never read it, transaction_execute.go
hardcoded 1, and the only writer of dvm.Version was the contract's own
VERSION() call (dvm_functions.go dvm_version). So the >=9.0.0 opcode gate was
author-declared, not chain-activated: any contract could self-activate the new
intrinsics the moment the code shipped.

Wire the chain version in and AND it with the contract's declared version:

  - ChainVersionFromHardFork(hf) maps the chain hard-fork integer to the DVM
    feature version: hf 1-3 (current mainnet) -> 8.0.0, 4 -> 9.0.0, 5 -> 10.0.0.
  - Shared_State gains ChainVersion, set by Execute_sc_function from the real
    hard_fork_version_current instead of the historical hardcoded 1.
  - Handle_Internal_Function requires the opcode's Range to be satisfied by
    BOTH dvm.Version (contract-declared) and dvm.State.ChainVersion (chain).
    A zero ChainVersion (off-chain/tooling execution) imposes no gate.
  - transaction_execute.go now passes chain.Get_Current_Version_at_Height.
  - The Simulator gets a version field (default 1, preserving old behavior) and
    uses it instead of the hardcoded 1; tests exercising v9 set s.version = 4.

So a contract that declares 9.0.0 cannot use verify_sig/ec_mul/etc until the
chain has actually activated them via a hard fork. Cheap opcodes are unaffected
(the >=0.0.0 ranges pass every chain version).

Tests: the hf->feature mapping, AND-gating at dispatch level (declared v9 +
pre-v9 chain rejected; v9 chain allowed; sha256 unaffected), an end-to-end
v9 program that fails on hf 3 and runs on hf 4, and the Fix C wallet-signed
authorization test now drives the simulator at hf 4.
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