Skip to content

I4: verify_adaptor — Schnorr adaptor verification in-VM (fixes #112, stacks on #84) - #113

Closed
liqdmetal wants to merge 5 commits into
DEROFDN:community-devfrom
liqdmetal:feature/dvm-i4-verify-adaptor
Closed

I4: verify_adaptor — Schnorr adaptor verification in-VM (fixes #112, stacks on #84)#113
liqdmetal wants to merge 5 commits into
DEROFDN:community-devfrom
liqdmetal:feature/dvm-i4-verify-adaptor

Conversation

@liqdmetal

Copy link
Copy Markdown

Summary

I4 from the intrinsic agenda (P0): verify_adaptor(pubkey_hex, message_hex, adaptor_sig_hex) -> Uint64 — Schnorr adaptor-signature verification in the VM, on DERO's own bn256 curve (no new dependencies). This is the cross-chain atomic settlement primitive (PTLC-style).

What it enables

A Schnorr adaptor signature proves "the holder of x (P = x·G) can produce a valid signature on m once a tweak t is revealed"without revealing t. Two chains share a pre-signed adaptor; whichever party reveals the tweak completes both transactions atomically. This is the settlement rail primitive for cross-chain atomic swaps:

  • DERO contract holds an adaptor-signed commitment
  • Counterparty (Bitcoin/Ethereum side) reveals the tweak to claim
  • The tweak simultaneously completes the DERO leg — no oracle, no trusted third party, no HTLC timeout race

Math

e = ReducedHash(R' || P || m)          (scalar mod bn256 order)
valid iff  s'·G == R' + e·P

where adaptor_sig = (s', R'): s' = r + e·x + t, R' = (r+t)·G. When the prover reveals t, the counterparty extracts the real signature: s = s' − t.

Serialization

  • pubkey_hex — 33-byte compressed G1
  • message_hex — raw message bytes
  • adaptor_sig_hex — 97 bytes = 64-byte big-endian s' + 33-byte compressed R'

Consensus safety

  • Pure verification: no witness material, no key recovery, no tweak disclosure enters the VM
  • Gated >= 10.0.0 (same HF window as verify_proof, L-series)
  • Malformed input → 0, never panics
  • 250k gas (verified: bn256 scalar mult + add + hash)

Tests (TestVerifyAdaptor)

Builds a real Schnorr adaptor signature (same construction the intrinsic verifies) and asserts:

  • valid adaptor → 1
  • wrong pubkey / wrong message / tampered s' → 0
  • malformed (bad lengths, invalid points) → 0, no panic
  • version gate: hidden from 9.0.0 contracts

Full dvm suite green.

Relationship


Branch: feature/dvm-i4-verify-adaptor in the fork liqdmetal/derohe-improvements-by-liqdmetal (stacked on feature/dvm-v9-intrinsics).

…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).
… in-VM

The cross-chain atomic primitive (PTLC-style), on DERO's own bn256 curve.

verify_adaptor(pubkey_hex, message_hex, adaptor_sig_hex) -> Uint64

A Schnorr adaptor signature proves 'the holder of x (P = x*G) can produce
a valid signature on m once a tweak t is revealed' WITHOUT revealing t.
Two chains share a pre-signed adaptor; whichever party reveals the tweak
completes both transactions atomically — the atomic-swap settlement
primitive for DERO.

Math (bn256, order n): e = ReducedHash(R'||P||m); valid iff s'*G == R' + e*P
Serialization: 33B pubkey + message + 97B adaptor sig (64B s' + 33B R').

Consensus-safety: pure verification (no witness material, no key
recovery, no tweak disclosure); gated >= 10.0.0; malformed input -> 0,
never panics.

TestVerifyAdaptor: builds a real adaptor sig (same construction), verifies
valid=1, wrong key/msg/tampered/malformed=0, version gate (9.0.0 hidden).

Full dvm suite green. Stacks on the v9 intrinsics (PR DEROFDN#84); 250k gas.
Two gaps pinned by TestWargameVerifyAdaptor*:

1. SCALAR MALLEABILITY: s' was taken raw from sig_bytes[:64] with no
   s' < n check. ScalarMult reduces mod n internally, so s' and s'+n
   produced the same point -> a non-canonical (s >= n) signature still
   verified as 1. Now rejected (low-s normalization) so the encoded
   signature is unique and non-malleable.

2. NON-CANONICAL POINT DECODE: P and R decoded with DecodeCompressed +
   err==nil, which accepts x >= p encodings (the chain-split class the
   I3/v9 PRs close with strictDecodeG1). Both now reject x >= p before
   decode, matching the strict decoder boundary.

Valid adaptor still verifies; 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