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
Conversation
This was referenced Aug 25, 2026
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
force-pushed
the
feat/dvm-intrinsics
branch
from
August 31, 2026 12:08
aa6a2b3 to
6d443d8
Compare
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#128 — DVM v9/v10 crypto intrinsics: budget + chain-activation gate
Branch:
feat/dvm-intrinsics(+ 2 follow-on commits onfeat/dvm-crypto-budget)Target:
DEROFDN/derohecommunity-devThis 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
verify_sig(pub, msg, sig)— Ed25519>=9.0.0hash_to_point(x)— bn256 HashToPoint>=9.0.0ec_add(P, Q)>=9.0.0ec_mul(P, n)>=9.0.0pedersen_commit(v, r)>=9.0.0verify_commit(v, r, C)>=9.0.0asset_balance(asset)>=9.0.0verify_adaptor(P, m, σ')— true Schnorr adaptor>=10.0.0The "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
sha256is 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 needec_mulat ~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:ceil(measured µs / 10 µs), measured in-VM, soCRYPTO_BUDGET_UNITS = 100caps worst-case CPU at ~1 ms for any opcode mix (a per-opcode cap would miss mixed programs).Shared_State, which every DVM shares, so nested/cross-contract calls can't refresh it by re-entering the VM.RunSmartContractinto an execution error — a revert, not a node abort.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_currentwas a dead parameter:Execute_sc_functionandSimulator.commonaccepted it but never read it,transaction_execute.gohardcoded1, and the only writer ofdvm.Versionwas the contract's ownVERSION()call. The>=9.0.0opcode gate was therefore author-declared, not chain-activated — any contract could self-activate these intrinsics the moment the code shipped.The fix.
Shared_StategainsChainVersion;Execute_sc_functionsets it from the real chain version;Handle_Internal_Functionrequires an opcode'sRangeto be satisfied by both the contract's declared version and the chain version.transaction_execute.gonow passeschain.Get_Current_Version_at_Height. The simulator gets aversionfield (default 1) so tests can choose their fork.Activation mapping (proposed; adjust to whichever fork ships these):
So today a contract declaring
9.0.0getsverify_sigrejected; the intrinsics only go live at a real hard fork. Cheap opcodes (>=0.0.0) are unaffected.Tests
TestCryptoBudgetTableWeights)ec_mulpass, 9th fails (TestCryptoBudgetBoundary)TestCryptoBudgetIsCombinationProof)TestCryptoBudgetPersistsAcrossNestedCalls)EC_MULrevert-free, 9 reverted (TestCryptoBudgetEndToEndProgram)TestChainVersionFromHardFork)TestChainVersionGatesIntrinsics)TestChainVersionGatesEndToEnd)TestFixC_WalletSignedAuthorization)BenchmarkCryptoIntrinsic*Consensus impact
>=9.0.0/>=10.0.0, and now additionally gated behind the chain hard fork.CRYPTO_BUDGET_UNITSare consensus parameters —crypto_budget.godocuments the measurement basis and that they must be re-measured before any change.Commits
bd3f33ffeat(dvm): weighted crypto budget for the expensive v9/v10 intrinsicsf775789feat(dvm): gate the v9/v10 intrinsics on the chain hard-fork version