feat: cross-chain bridging research (attested + SPV + HTLC) - #56
Conversation
Replicate the Near Intents deposit/withdrawal model (PoA bridge: omft.near factory mint on attested deposits, WITHDRAW_TO burn on withdrawals) as Arkade covenant contracts: - examples/bridge/bridge_mint.ark: deposit leg. k-of-n custodian quorum attests sha256(depositId || recipientSpk || amount || nonce) via checkSigFromStack; covenant mints exactly the attested amount of the wrapped asset under control-asset accounting, pins the recipient output to the attested script pubkey (enabling deposits directly into swap offers), and advances a monotonic nonce through state continuation for single-use attestations. - examples/bridge/bridge_withdrawal.ark: withdrawal leg. Per-withdrawal escrow committing to destHash + unique withdrawalId; quorum-attested release() burns the wrapped tokens after destination-chain payout, and a consensus-enforced refund() after refundTime bounds custodial risk (an improvement over Near's operational-only refunds). - examples/bridge/README.md: study of the Near Intents architecture (Verifier/intents.near, PoA bridge, Omni Bridge) with the mapping to Arkade, trust model comparison, and future work. - tests: behavioral assertions (quorum unrolling, attestation reconstruction, supply checks, CLTV refund) plus roundtrip coverage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ttf3oxgKCxBsYWJrQYfUfE
Rewrite the bridge contract comments and README in an affirmative, protocol-agnostic voice (no third-party protocol references): - Present deposit/withdrawal as an advanced bridge design leading with what the covenant enforces on-chain. - Add an honest trust-model section: foreign-chain custody is trusted and unavoidable; enumerate exactly what a colluding quorum can and cannot do. - Add an opsec section explaining why attest-only signing (custodians sign a small fixed message via checkSigFromStack, not the live tx) keeps keys air-gapped, makes signers non-interactive, enables permissionless relay, and caps the blast radius via the covenant. Contract logic is unchanged; tests still pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ttf3oxgKCxBsYWJrQYfUfE
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Playground PreviewA live preview of this PR's playground is available at:
|
Replace the custodian quorum on the deposit leg with an on-chain SPV proof: a Bitcoin/PoW deposit is credited by verifying merkle inclusion + proof-of-work + confirmation depth in the covenant itself, with no signer. Compiler primitives (SUPERVISED files — grammar/models/compiler): - Expose hash256() and reverseBytes() as first-class expressions (let, assignment, byte operands), not just inside require(hash256(x)==y). hash256 -> OP_HASH256 (Bitcoin double-SHA256, confirmed against arkade-os/emulator pkg/arkade/opcode.go); reverseBytes -> OP_REVERSEBYTES. - Add OP_REVERSEBYTES constant; wire Hash256/ReverseBytes through parser, models, typechecker, validator, and the concat (+ -> OP_CAT) rewrite so hash256(node + sibling) lowers its inner + to a merkle-step concat. examples/bridge/bridge_spv.ark (BridgeSpv): - merkle inclusion via a hand-rolled hash256+cat fold with per-level direction bits (OP_MERKLEBRANCHVERIFY is tagged-hash/sorted-pair and cannot verify Bitcoin trees); - single-header PoW as an unsigned 256-bit BigNum compare (0x00 sign-byte guard); confirmation headers chained by prevHash, each PoW-checked; - payment binding: hash256(tx)==txid and the paid output matches the watched depositScript for >= mintAmount; - mint under control-asset accounting with nonce state continuation. Trust model documented honestly: difficulty is governance-pinned (no on-chain retarget), header relaying is a liveness role, finality is economic. SPV trades a k-of-n signer set for PoW + pinned-difficulty + relayer liveness. README compares both deposit legs and lists v1 limits (replay registry, 256-bit compare needs the PR #51 BigNum opcode sync). Tests: 6 SPV bridge assertions (no-signature proof path, merkle/PoW opcodes, control-asset mint, witness flattening) + roundtrip; 3 feature tests for the new primitives. cargo fmt clean; full suite green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ttf3oxgKCxBsYWJrQYfUfE
Add the solver fast-transfer route: a one-shot cross-chain swap with no wrapped token and no bridge treasury, chained by a single Lightning payment secret so no chain verifies another chain's consensus. examples/bridge/swap_htlc.ark (SwapHtlc): - claim(preimage) covenant: SHA256 hashlock (matches the LN payment hash so one secret unlocks the LN, Arkade, and EVM legs) + payout pinned by introspection to solverPk. No beneficiary signature, so any relayer/watchtower/operator can complete it once the secret is public and it can only pay the solver. - refund() covenant: after refundTime the BTC returns, pinned to userPk; likewise permissionless and non-redirectable. - L1 claim/refund tapscript leaves re-enforce the hashlock/timelock; unilateral CSV exit for the user (funder). Introspection removes *beneficiary* liveness (delegatable completion), but the free option is inherent to cross-ledger swaps and is documented as priced-away (short T_evm + taker premium), not covenant-removable. README: comprehensive fast-transfer section with the LN -> BTC on Arkade -> LVGA on SwissLedger worked example, timelock-ordering table, liveness/free- option analysis, and a three-shape trust comparison (wrap bridge vs SPV vs fast-transfer swap). Tests: 6 swap-HTLC assertions (structure, solver-pinned hashlocked claim, no-beneficiary-signature claim, user-pinned timelocked refund, L1 hashlock leaf, CSV unilateral) + roundtrip. cargo fmt clean; full suite green; playground regenerated (26 contracts). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ttf3oxgKCxBsYWJrQYfUfE
Add a merchant-payment bridge-out where real LVGA lives in a SwissLedger (EVM) liquidity pool and wrapped wLVGA is issued on Arkade against it. Paying out is a BURN: the burn plus a merchant commitment is the whole authorization -- no Arkade-side quorum signs the release. examples/bridge/wlvga_burn.ark (WlvgaBurn): - burnOut() verifies a merchant authorization with checkSigFromStack over sha256(protocolTag || merchantEvmAddr || amount || burnNonce), shrinks wLVGA supply by amount, and pins an OP_RETURN output committing protocolTag || merchantEvmAddr || num2bin(amount, 8). - One secp256k1 key, two chains: the same merchant key/message is verified by checkSigFromStack on Arkade and by ecrecover on the SwissLedger pool, which matches the recovered address to the committed evmAddr and releases LVGA. Relay is permissionless (commitment + sig are self-authenticating); the monitoring node/webhook is liveness only. - pubkey/signature stay strictly inside checkSigFromStack (never in a concatenation), so every hashed/committed field is bytes/int and the + operator lowers to OP_CAT -- the digest matches the merchant's off-chain signing and the EVM reconstruction byte-for-byte. Replay bounded by burnNonce. README: burn-to-unlock section with the BTC -> wLVGA -> burn -> webhook -> ecrecover -> LVGA flow, the one-key-two-chains explanation, the OP_RETURN commitment format, and a trust model (release is on-chain/trustless; residual trust is pool solvency + relayer liveness). Framed as the mirror of the SPV deposit leg (inbound proof vs outbound burn). Trust-comparison table extended to four shapes. Tests: 6 burn-out assertions (structure, CSFS merchant auth, concat-not-add message lowering, supply-shrink accounting, OP_RETURN commitment pinning) + roundtrip. cargo fmt clean; full suite green; playground regenerated (27). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ttf3oxgKCxBsYWJrQYfUfE
4b035fb to
7cb95d4
Compare
Rework the bridge-out for the real deployment: the BTC side and the LVGA liquidity provider (LP) are SEPARATE parties, and LVGA is a transfer-restricted token that can never be redeemed for wLVGA. Rename wlvga_burn.ark -> wlvga_payout.ark (WlvgaPayout) with two modes: - payOut() (default, two parties): transfer the BTC-backed wLVGA claim to the LP (SingleSig(lpPk)) instead of burning it, so the LP is compensated in BTC for the LVGA it releases and the two roles may be distinct entities. Closes the solvency loop. - burnOut() (option, same party): burn the wLVGA; only solvent when the LP is also the party that received the BTC at mint time (integrated MM). Both modes authorize the payee with the merchant's secp256k1 key via checkSigFromStack (mirrored by ecrecover on the SwissLedger pool) and pin the OP_RETURN commitment protocolTag || merchantEvmAddr || num2bin(amount, 8). Economics documented honestly: LVGA is one-way (whitelisted holders, pool -> merchant only); wLVGA is a BTC-backed BEARER claim, never redeemed for LVGA. The LP is a market maker taking CHF/BTC exposure for fees -- it gives up LVGA (CHF), receives BTC (hedgeable to USDT), and prices fees into its quote; the covenant guarantees it is paid in BTC for every payout it services. The BTC and LVGA sides connect only through the LP's balance sheet. Tests updated to the two-mode contract (payOut transfers to LP with no supply-shrink; burnOut shrinks supply; both carry CSFS + concatenated message + pinned commitment) + roundtrip. cargo fmt clean; full suite green; playground regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ttf3oxgKCxBsYWJrQYfUfE
Add examples/bridge/swissledger_pool.md: the destination-chain (EVM) pool contract that auto-releases LVGA against an Arkade wLVGA payout, removing per-payment operator discretion. Flavor A (trusted reporter): a reporter attests the Arkade burn/transfer happened; the contract independently re-binds destination, amount, and uniqueness so the reporter is trusted for LIVENESS only, not correctness: - ecrecover(payoutMsg, sig) == committed merchantEvmAddr (no redirect) - LVGA whitelist check (transfer restriction) - paid[burnNonce] mapping (no replay/double-pay) With reporter = pool operator the trust is incentive-aligned: a false report drains the operator's own pool for no BTC, so the practical residual is censorship/liveness, mitigated by permissionless/multiple reporters. Spec pins the exact byte layout to match wlvga_payout.ark (protocolTag || merchantEvmAddr(20) || amount(8 LE) || burnNonce(8 LE), sha256) and flags the two integration gotchas: little-endian 64-bit ints, and a recoverable secp256k1 ECDSA signature over the raw sha256 digest (not EIP-191/keccak) so one merchant key verifies via checkSigFromStack on Arkade and ecrecover on EVM. Includes a reference Solidity sketch and contrasts Flavor B (on-EVM Bitcoin SPV proof, no reporter trust). No Arkade change: wlvga_payout.ark already emits the full commitment. README: add an "Automated release on SwissLedger" subsection and update the payout trust table (release automation row). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ttf3oxgKCxBsYWJrQYfUfE
…ments) Add examples/stability/stability_lvga_payout.ark (StabilityPayout): a BTC-collateralized, oracle-marked CHF claim that settles directly as a merchant LVGA payment, fusing the stability vault settlement with the wLVGA payout bridge-out. seekerPayout(): - verifies the CHF/BTC oracle price (checkSigFromStack over sha256(ticker || price || time), 600s freshness); - accrues funding and applies the exit fee -> payoutCHF; - verifies a merchant invoice (checkSigFromStack, mirrored by ecrecover on the SwissLedger pool) and commits payoutCHF in the OP_RETURN; - routes the seeker's BTC entitlement at the oracle price (seekerRaw) to the LP as compensation, with the collateral remainder returning to the provider (clamped when the provider is fully drawn down). The merchant receives exactly the claim's CHF value regardless of BTC moves during the holding period; the same oracle prices both sides. Price risk lives with the vault PROVIDER (funded, over-collateralized, oracle-marked), not the bridge LP or the user. Set lpPk = providerPk to run both as one delta-neutral market maker; keep distinct for two independent parties. Focused variant -- full vault lifecycle stays in stability_vault.ark (tests intact). README: "Stable-value payouts" subsection tying BTC -> CHF vault claim -> LVGA merchant payment into one rail, plus a Design bullet. Tests: 5 assertions (structure, oracle+merchant CSFS + seeker checkSig, seeker BTC pinned to LP / remainder to provider / not to seeker, OP_RETURN commitment + 7 concats) + roundtrip. cargo fmt clean; full suite green; playground regenerated (28 contracts). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ttf3oxgKCxBsYWJrQYfUfE
…rch PR Keep this PR to the general cross-chain bridging research and move the LVGA-specific productization to a separate PR. Removed here (now on claude/lvga-bridge-stability): - examples/bridge/wlvga_payout.ark - examples/bridge/swissledger_pool.md - examples/stability/stability_lvga_payout.ark - their tests + roundtrip entries + module registration Also genericized the swap_htlc worked example from LVGA/SwissLedger to a generic EVM token/chain, so this PR carries no LVGA specifics. The fast-transfer HTLC swap itself is a general primitive and stays here. Remaining scope: bridge_mint, bridge_withdrawal, bridge_spv, swap_htlc, the hash256/reverseBytes compiler primitives, and the three-shape trust model. cargo fmt clean; full suite green (155 example + 169 feature); playground regenerated (26 contracts). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ttf3oxgKCxBsYWJrQYfUfE
Summary
Research contracts exploring the cross-chain bridging design space for Arkade, from a trusted-quorum wrap bridge to a fully trustless on-chain SPV deposit to a fast-transfer HTLC swap. Each shape is documented with an honest trust model so a deployment can pick the right one per chain. Also adds the small compiler primitives the SPV path needs (
hash256()/reverseBytes()as first-class expressions).Contracts (
examples/bridge/)bridge_mint.arkbridge_withdrawal.arkbridge_spv.arkswap_htlc.arkHighlights
bridge_spv.arkverifies a Bitcoin/PoW deposit entirely on-chain: a hand-rolled merkle fold (hash256+cat, per-level direction bit —OP_MERKLEBRANCHVERIFYis tagged-hash and unusable for Bitcoin trees), a single-header PoW check as an unsigned 256-bit BigNum compare, confirmation-depth headers, and a payment binding, then mints under control-asset accounting. Difficulty is governance-pinned (on-chain retarget is infeasible); documented honestly.swap_htlc.arkchains LN, Arkade, and an EVM HTLC with a single SHA256 payment hash, so no chain verifies another's consensus. Introspection pins the payout, making completion permissionless (watchtower/operator) — removing beneficiary liveness while leaving the free option (inherent to cross-ledger swaps) intact and documented.Compiler primitives (supervised files)
To express the SPV merkle fold and PoW check,
hash256()andreverseBytes()are exposed as first-class expressions (previouslyhash256only parsed insiderequire(hash256(x) == y)). Wired through grammar, parser, models, typechecker, validator, and the+→OP_CATconcat rewrite;OP_REVERSEBYTESconstant added. Verified againstarkade-os/emulator(pkg/arkade/opcode.go):OP_HASH256is Bitcoin double-SHA256. These touchgrammar.pest/models/compiler, which are supervised — flagging for review.Testing
cargo fmt --checkclean; full suite green (155 example + 169 feature tests); playground regenerated (26 contracts).Open item
The SPV 256-bit PoW compare emits BigNum comparisons that require the opcode-emission sync in #51 (standard
OP_LESSTHANOREQUALon BigNums, not the removed Elements*64family). This branch should rebase onto / land after #51 for correct 256-bit compares; nothing else depends on it.🤖 Generated with Claude Code
https://claude.ai/code/session_01Ttf3oxgKCxBsYWJrQYfUfE