I3: ec_mul intrinsic — point scalar multiplication (fixes #104, stacks on #84) - #105
Closed
liqdmetal wants to merge 6 commits into
Closed
I3: ec_mul intrinsic — point scalar multiplication (fixes #104, stacks on #84)#105liqdmetal wants to merge 6 commits into
liqdmetal wants to merge 6 commits into
Conversation
…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.
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).
ec_mul(point_hex String, scalar Uint64) -> String (33-byte compressed point, hex), gated >= 9.0.0, 30k gas. The homomorphic counterpart to ec_add (I2): combined, a contract can build and accumulate commitments entirely in-VM — point derivation, key blinding, commitment scaling. Completes the group-arithmetic pair the DVM v9 package started. TestEcMul verifies: ec_mul(c,2) == ec_add(c,c); scalar composition (3*2 == 6); ec_mul(c,1) == c; ec_mul(c,0) valid encoding; output is always a 33-byte compressed point; version gate (1.2.3 rejected).
ec_mul decodes its compressed-point input with DecodeCompressed and only checks err == nil. bn256/changes.go computes y from x mod p (xToY) but stores the RAW x — so an x>=p encoding decodes 'successfully' to an on-curve point (x reduced) where a strict decoder REJECTS the encoding. The intrinsic therefore accepts x>=p input and computes a deterministic result, while the clean-room Rust port would reject at decode -> the two implementations diverge on contract behavior -> chain-split class bug (the exact class the conformance PR documents). Pinned: ec_mul(x_gt_p, 2) returns a result (point-at-infinity encoding) instead of rejecting. Fix direction: strict x < p validation after decode in ec_mul/ec_add/verify_commit so every implementation accepts and rejects identically.
Wargame finding pinned by TestWargameEcMulAcceptsOffCurvePoint: the point intrinsics decoded caller-supplied compressed points with DecodeCompressed + err==nil, which accepts x>=p encodings (bn256/changes.go computes y from x mod p but stores the raw x). The intrinsic then computed on the point where a strict decoder (clean-room Rust) rejects the encoding -> implementations diverge on contract behavior -> chain-split class bug. Fix: strictDecodeG1 validates x < p (canonical field encoding) before DecodeCompressed, used by ec_mul, ec_add, and verify_commit. Off-curve / non-canonical input now panics (recovered -> deterministic tx failure), matching the strict decoder. Canonical points unaffected; full dvm suite green.
Author
|
Superseded by PR #128 — the consolidated intrinsics package. Same code, one reviewable PR with no vendor noise. |
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.
Summary
I3 from the intrinsic agenda:
ec_mul(point_hex String, scalar Uint64) -> String— point scalar multiplication for the DVM, gated>= 9.0.0, 30k gas.What it does
Multiplies a compressed bn256 G1 point (DERO's 33-byte encoding) by a uint64 scalar, returning the compressed result. The homomorphic counterpart to
ec_add(I2, already in DVM v9 PR #84):ec_add(c1, c2)ec_mul(c, k)Together they give a contract complete in-VM group arithmetic on commitments — point derivation for key blinding, commitment scaling, and accumulation — without oracle dependence.
Tests (
TestEcMul)ec_mul(c, 2) == ec_add(c, c)— the homomorphic pairec_mul(ec_mul(c, 3), 2) == ec_mul(c, 6)ec_mul(c, 1) == c(identity)ec_mul(c, 0)produces a valid point encoding1.2.3contractsHardening (wargame finding, now in the code)
Strict point decoding —
strictDecodeG1.ec_mul(and its siblingsec_add,verify_commitin PR #84) decode the caller-supplied compressed point withDecodeCompressed+err == nil. The conformance PR (#86) documented that Go's decoder accepts x ≥ p encodings — it computes y from x mod p but stores the raw x. So a contract could feed an off-curve (x ≥ p) point and the intrinsic would compute on it, while a strict decoder (the clean-room Rust port) rejects the encoding → the two implementations diverge on contract behavior → chain-split class bug.strictDecodeG1now validatesx < p(canonical field encoding) beforeDecodeCompressed, applied toec_mul,ec_add, andverify_commit. Non-canonical input panics (recovered → deterministic tx failure), matching the strict decoder. Canonical points are unaffected.Pinned by
TestWargameEcMulAcceptsOffCurvePoint(proves the fix: off-curve input now rejected, canonical input still accepted) and in the derohe-rs differential harness (strict_point_decode, 8 vectors).Relationship
>= 9.0.0gate, same group-arithmetic familyec_add/ec_mulpair; the AMM-reserve/confidential-settlement use case is now fully expressible in-VMBranch:
feature/dvm-i3-ecmulin the forkliqdmetal/derohe-improvements-by-liqdmetal(stacked onfeature/dvm-v9-intrinsics).