Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
065eef2
cardano: add contracts folder with README scaffold
tiljrd Aug 23, 2025
f49bca7
cardano: initialize Aiken project config (aiken.toml)
tiljrd Aug 23, 2025
61b06a9
cardano: add shared Types and Codec modules (packet header v1, payloa…
tiljrd Aug 23, 2025
27cf9db
cardano: scaffold Endpoint validator and datum/redeemer types
tiljrd Aug 23, 2025
245d88e
cardano: scaffold ULN302 send validator and config types
tiljrd Aug 23, 2025
e2342b9
cardano: scaffold ULN302 receive validator with commit/verify entrypo…
tiljrd Aug 23, 2025
cd5d1f2
cardano: scaffold Executor validator with commitAndExecute entrypoint
tiljrd Aug 23, 2025
f436ed8
cardano: scaffold DVN validator with quorum-related redeemers
tiljrd Aug 23, 2025
41665b3
cardano(aiken): update aiken.toml to current schema (plutus v3, stdli…
tiljrd Aug 23, 2025
2a23994
cardano(aiken): use lowercase module paths; add lib/layerzero types &…
tiljrd Aug 23, 2025
0c32a38
cardano(aiken): switch to lowercase module alias 'types' and remove o…
tiljrd Aug 23, 2025
94a8bc1
cardano(aiken): convert dvn to validator block syntax; fix generics a…
tiljrd Aug 23, 2025
c3caf07
cardano(aiken): adopt current validator syntax (spend/else, standard …
tiljrd Aug 23, 2025
f1faf5c
cardano(aiken): remove builtin.trace from validators to satisfy parser
tiljrd Aug 23, 2025
b89c338
cardano(aiken): switch pattern matches from to for enum variants
tiljrd Aug 23, 2025
b09684e
cardano(aiken): replace 'case' with 'when ... is' for redeemer patter…
tiljrd Aug 23, 2025
4eccc4b
cardano(aiken): use builtin.blake2b_256; track aiken.lock
tiljrd Aug 23, 2025
96fa1fc
cardano(aiken): name OutputReference/Transaction args uniquely (_0, _…
tiljrd Aug 23, 2025
83ef005
cardano(aiken): destructure CommitAndExecute record in executor valid…
tiljrd Aug 23, 2025
9e9bda7
cardano(aiken): import bytearray from aiken/primitive/bytearray
tiljrd Aug 23, 2025
9a75405
cardano(aiken): qualify type constructors in codec; use bytearray.fro…
tiljrd Aug 23, 2025
c67724e
cardano(aiken): import constructors and pattern-match inside function…
tiljrd Aug 23, 2025
29af9bc
cardano(aiken): use labeled record patterns in when-branches across v…
tiljrd Aug 23, 2025
21c94ee
cardano(aiken): use builtin.serialise_data for Eid/Nonce encoding
tiljrd Aug 23, 2025
c4959d8
cardano(aiken): use capitalized True in validator match branches
tiljrd Aug 23, 2025
13e0881
cardano(aiken): remove invalid/unused aiken/list import
tiljrd Aug 23, 2025
edfff37
cardano(aiken): refactor codec pattern-matching to let-bindings and a…
tiljrd Aug 23, 2025
a8d2a4a
cardano(aiken): fix codec to pad Address20 using from_int_big_endian(…
tiljrd Aug 23, 2025
0dda3d8
cardano(aiken): add codec.assert_header_v1 and header_receiver_b20; w…
tiljrd Aug 23, 2025
54c8a36
cardano(docs): expand README with architecture, state model, flows, a…
tiljrd Aug 23, 2025
03bbf9d
cardano(aiken/tests): add test for codec.header_receiver_b20 to confi…
tiljrd Aug 23, 2025
e4c49a4
cardano(aiken/codec): add header field helpers + constants module (lo…
tiljrd Aug 23, 2025
e024489
cardano(endpoint/helpers): add pure helpers for nonce monotonicity an…
tiljrd Aug 23, 2025
45f0662
cardano(aiken/codec): add parse_header_v1 and round-trip test
tiljrd Aug 23, 2025
93a8fb8
cardano(aiken): remove unused import/binding in codec; add dvn_helper…
tiljrd Aug 23, 2025
a612064
cardano(endpoint): refactor datum to Global|Channel and enforce nonce…
tiljrd Aug 23, 2025
77c41ab
cardano(aiken/validators): fix datum reference in uln302_receive; kee…
tiljrd Aug 23, 2025
1ce8b95
cardano(aiken): make header constants public; silence unused pattern …
tiljrd Aug 23, 2025
795a9e9
cardano(aiken/tests): strengthen codec tests for wrong length/version…
tiljrd Aug 23, 2025
99a1ce9
cardano(aiken/codec,tests): use builtin.blake2b_256 for header_hash_v…
tiljrd Aug 23, 2025
b93f6a1
cardano(aiken): use builtin.head_list/tail_list for Data lists in att…
tiljrd Aug 23, 2025
c6dfc68
cardano(aiken/types): stage previously modified types; keep shared At…
tiljrd Aug 23, 2025
77c199d
cardano(aiken): remove unused import and unused _idx binding in attes…
tiljrd Aug 23, 2025
a5de8d6
cardano(aiken/tests): add ULN302 Receive tests for attestation parsin…
tiljrd Aug 23, 2025
2bc73b1
cardano(aiken/tests): add DVN Execute tests for quorum satisfied/unsa…
tiljrd Aug 23, 2025
6514904
cardano(aiken/tests): add Endpoint validator tests for Verify and LzR…
tiljrd Aug 23, 2025
177a4b1
cardano(aiken/uln302_send): validate outbound instruction InlineDatum…
tiljrd Aug 23, 2025
37a498d
cardano(docs): document OutboundDatum and AttestationDatum InlineDatu…
tiljrd Aug 23, 2025
fb7786e
cardano(aiken/tests): add ULN302 Receive CommitVerification header as…
tiljrd Aug 23, 2025
ce2b98b
cardano(aiken/tests): add Executor CommitAndExecute minimal tests (ac…
tiljrd Aug 23, 2025
46417c3
cardano(executor): enforce native drop coverage with total outputs lo…
tiljrd Aug 23, 2025
ab9e3bb
cardano(uln302/receive): fix type mismatch by unwrapping Nonce to Int…
tiljrd Aug 23, 2025
f3e0b76
cardano(aiken): update lockfile
tiljrd Aug 23, 2025
1d3fec4
cardano(aiken): tidy warnings (executor let-pattern; silence unused v…
tiljrd Aug 23, 2025
faa5664
cardano(dvn_helpers): replace local recursion in unique_signers with …
tiljrd Aug 23, 2025
6e94920
cardano(uln302/receive): clean ReceiveRedeemer; split find_sigs; repl…
tiljrd Aug 23, 2025
09e8b0b
cardano(dvn): close parse_attestation; replace list.find_map with fol…
tiljrd Aug 23, 2025
33dc26a
cardano(uln302_send,executor): close sum types; rename datum param; m…
tiljrd Aug 23, 2025
fcf1e24
cardano(tests): sync tests with validator signatures and parsing fixes
tiljrd Aug 23, 2025
bdb9e30
cardano(types): keep in sync with new DVN/ULN structures (minor updates)
tiljrd Aug 23, 2025
7068a17
cardano(executor): require Endpoint channel prev input (executed=True…
tiljrd Aug 23, 2025
02fab06
cardano(dvn): add admin key gating for signer/quorum updates; cardano…
tiljrd Aug 23, 2025
96a2995
cardano(uln302_receive): dedup DVN signer helpers; fix find_sigs; com…
tiljrd Aug 23, 2025
e7b2cf4
cardano(uln302_receive): source quorum from DVN reference input; keep…
tiljrd Aug 23, 2025
9e52d30
cardano(docs): document DVN quorum via reference input, ULN Send fee …
tiljrd Aug 23, 2025
d550f0b
cardano(tests): update ULN302 Send tests for VKH fee recipients; adju…
tiljrd Aug 23, 2025
cacbaab
cardano(tests): ULN302 Receive positive-path with real Ed25519 signat…
tiljrd Aug 23, 2025
a245754
cardano(docs): document SigsDatum schema and ULN302 Receive/DVN verif…
tiljrd Aug 23, 2025
a2e4f82
cardano(tests): DVN admin flows (SetSignerSet, SetQuorum) and quorum …
tiljrd Aug 23, 2025
45e8280
cardano(cleanup): minor validator tidy and warnings reduction (no log…
tiljrd Aug 23, 2025
d34227b
cardano(aiken): remove discarded let-bindings in ULN302 Receive parse…
tiljrd Aug 23, 2025
110128e
docs(cardano/aiken): concise README with structure and how to run tests
tiljrd Aug 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions src/cardano/contracts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Cardano LayerZero (Aiken)

This folder contains the Aiken-based Cardano implementation of LayerZero V2 core on-chain components.

Contents
- aiken/
- validators/
- endpoint.ak
- uln302_send.ak
- uln302_receive.ak
- executor.ak
- dvn.ak
- lib/
- layerzero/
- types.ak
- codec.ak
- constants.ak
- dvn_helpers.ak
- endpoint_helpers.ak
- tests/
- codec.tests.ak
- endpoint.tests.ak
- endpoint_validator.tests.ak
- uln302_send.tests.ak
- uln302_receive.tests.ak
- executor_commit.tests.ak
- dvn.tests.ak
- dvn_execute.tests.ak
- aiken.toml

What’s implemented
- Endpoint: per-(origin, receiver) channel UTXOs for inbound nonce and executable-state tracking.
- ULN302 Send/Receive: config handling, header assertions, atomic Endpoint.Verify requirement, fee routing checks.
- Executor: Commit-and-Execute with Endpoint IO atomicity and fee-output enforcement.
- DVN: Ed25519 multisig with configurable signer set and quorum; admin flows for signer/quorum updates.
- Codecs: Packet V1 header encoder/decoder and hashing aligned to EVM fixed-width layout.

How to run
- Install Aiken: https://aiken-lang.org/
- From this directory:
- cd src/cardano/contracts/aiken
- aiken check

This compiles and collects the test suite, which includes:
- DVN multisig verification (positive with real Ed25519 signature) and negative cases (insufficient quorum, non-member, duplicates).
- Endpoint nonce monotonicity and executable gating.
- ULN302 Send outbound datum and fee routing checks.
- Executor commit-and-execute atomicity.

Notes
- No secrets are embedded; tests use deterministic vectors.
- Behavior mirrors LayerZero V2 EVM/altVM where applicable, adapted to Cardano’s eUTXO model.
16 changes: 16 additions & 0 deletions src/cardano/contracts/aiken/aiken.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# This file was generated by Aiken
# You typically do not need to edit this file

[[requirements]]
name = "aiken-lang/stdlib"
version = "v2"
source = "github"

[[packages]]
name = "aiken-lang/stdlib"
version = "v2"
requirements = []
source = "github"

[etags]
"aiken-lang/stdlib@v2" = [{ secs_since_epoch = 1755954588, nanos_since_epoch = 724920106 }, "25c8d0802b8266feca04b47933382c5dee3cadb422208a5d3810d9d2df108c2e"]
10 changes: 10 additions & 0 deletions src/cardano/contracts/aiken/aiken.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name = "LZeroAnalytics/layerzero-cardano"
version = "0.1.0"
plutusVersion = "v3"
licences = ["MIT"]
description = "LayerZero V2 core contracts adapted for Cardano in Aiken (Endpoint, ULN302, Executor, DVN)."

[[dependencies]]
name = "aiken-lang/stdlib"
version = "v2"
source = "github"
133 changes: 133 additions & 0 deletions src/cardano/contracts/aiken/lib/layerzero/codec.ak
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use aiken/builtin
use aiken/builtin
use aiken/primitive/bytearray

use layerzero/types
use layerzero/types.{Address32, Address20, Eid, Nonce, Guid, PacketHeaderV1}

pub fn header_hash_v1(header: ByteArray) -> ByteArray {
builtin.blake2b_256(header)
}

pub fn encode_address32(a) -> ByteArray {
let Address32(bytes) = a
bytes
}

pub fn encode_address20_to_bytes32(a) -> ByteArray {
let Address20(b20) = a
let pad = bytearray.from_int_big_endian(0, 12)
bytearray.concat(pad, b20)
}

pub fn encode_eid(a) -> ByteArray {
let Eid(e) = a
bytearray.from_int_big_endian(e, 4)
}

pub fn encode_nonce(a) -> ByteArray {
let Nonce(n) = a
bytearray.from_int_big_endian(n, 8)
}

pub fn encode_header_v1(h: types.PacketHeaderV1) -> ByteArray {
let b0 = bytearray.from_int_big_endian(1, 1)
let b1 = encode_nonce(h.nonce)
let b2 = encode_eid(h.src_eid)
let b3 = encode_address32(h.sender)
let b4 = encode_eid(h.dst_eid)
let b5 = encode_address20_to_bytes32(h.receiver_b20)
let b01 = bytearray.concat(b0, b1)
let b23 = bytearray.concat(b2, b3)
let b45 = bytearray.concat(b4, b5)
bytearray.concat(bytearray.concat(b01, b23), b45)
}

pub fn payload_hash(guid: types.Guid, message: ByteArray) -> ByteArray {
builtin.blake2b_256(bytearray.concat(unwrap_guid(guid), message))
}

pub fn header_version(header: ByteArray) -> ByteArray {
bytearray.slice(header, 0, 1)
}

pub fn header_nonce_be(header: ByteArray) -> ByteArray {
bytearray.slice(header, 1, 9)
}

pub fn header_src_eid_be(header: ByteArray) -> ByteArray {
bytearray.slice(header, 9, 13)
}

pub fn header_sender_b32(header: ByteArray) -> ByteArray {
bytearray.slice(header, 13, 45)
}

pub fn header_dst_eid_be(header: ByteArray) -> ByteArray {
bytearray.slice(header, 45, 49)
}

pub fn hash_header(header: ByteArray) -> ByteArray {
builtin.blake2b_256(header)
}

pub fn header_dst_eid(header: ByteArray) -> types.Eid {
let be = header_dst_eid_be(header)
types.Eid(bytearray.to_int_big_endian(be))
}

pub fn header_nonce(header: ByteArray) -> types.Nonce {
let be = header_nonce_be(header)
types.Nonce(bytearray.to_int_big_endian(be))
}

pub fn parse_header_v1(header: ByteArray) -> Option<types.PacketHeaderV1> {
if bytearray.length(header) != 81 {
None
} else {
let ver = header_version(header)
if ver == bytearray.from_int_big_endian(1, 1) {
let nonce = header_nonce(header)
let src_eid = types.Eid(bytearray.to_int_big_endian(header_src_eid_be(header)))
let sender_b32 = header_sender_b32(header)
let dst_eid = header_dst_eid(header)
let recv20 = header_receiver_b20(header)
Some(types.PacketHeaderV1 {
nonce: nonce,
src_eid: src_eid,
sender: types.Address32(sender_b32),
dst_eid: dst_eid,
receiver_b20: recv20,
})
} else {
None
}
}
}



fn unwrap_guid(g) -> ByteArray {
let Guid(b) = g
b
}
pub fn assert_header_v1(header: ByteArray, local_eid: types.Eid) -> Bool {
// length must be 81 bytes as per EVM PacketV1 header
if bytearray.length(header) != 81 {
False
} else {
let version = bytearray.slice(header, 0, 1)
let expected_version = bytearray.from_int_big_endian(1, 1)
let dst_eid_be = bytearray.slice(header, 45, 49)
let types.Eid(eid_int) = local_eid
let expected_dst_eid = bytearray.from_int_big_endian(eid_int, 4)
version == expected_version && dst_eid_be == expected_dst_eid
}
}

pub fn header_receiver_b20(header: ByteArray) -> types.Address20 {
// receiver is bytes32 at offset 49..81; take last 20 bytes
let receiver_b32 = bytearray.slice(header, 49, 81)
let b20 = bytearray.slice(receiver_b32, 12, 32)
types.Address20(b20)
}
2 changes: 2 additions & 0 deletions src/cardano/contracts/aiken/lib/layerzero/constants.ak
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub const header_v1_version = 1
pub const header_v1_len = 81
62 changes: 62 additions & 0 deletions src/cardano/contracts/aiken/lib/layerzero/dvn_helpers.ak
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use aiken/collection/list
use aiken/primitive/bytearray
use aiken/builtin

use layerzero/types

pub fn quorum_satisfied(sc: Int, q: Int) -> Bool {
sc >= q && q > 0
}

pub fn attestation_message(hh: ByteArray, ph: ByteArray) -> ByteArray {
bytearray.concat(hh, ph)
}

pub fn unique_signers(pubs: List<types.Address32>) -> Bool {
let seen =
list.foldr(
pubs,
[],
fn(p, acc) {
let types.Address32(b) = p
if list.any(acc, fn(x) { x == b }) {
acc
} else {
[b, ..acc]
}
},
)
list.length(seen) == list.length(pubs)
}

pub fn signers_within_set(pubs: List<types.Address32>, set: List<types.Address32>) -> Bool {
pubs
|> list.all(fn(p) {
let types.Address32(b) = p
list.any(set, fn(s) {
let types.Address32(sb) = s
sb == b
})
})
}

pub fn verify_multisig(
message: ByteArray,
pubs: List<types.Address32>,
sigs: List<ByteArray>,
quorum: Int,
) -> Bool {
let pairs = list.zip(pubs, sigs)
let valid = list.foldr(
pairs,
0,
fn(pair, acc) {
let p = pair.1st
let s = pair.2nd
let types.Address32(pk) = p
let ok = builtin.verify_ed25519_signature(pk, message, s)
acc + if ok { 1 } else { 0 }
},
)
valid >= quorum && quorum > 0
}
38 changes: 38 additions & 0 deletions src/cardano/contracts/aiken/lib/layerzero/endpoint_helpers.ak
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use layerzero/types

pub fn nonce_next_ok(current: types.Nonce, incoming: types.Nonce) -> Bool {
let types.Nonce(c) = current
let types.Nonce(n) = incoming
n == c + 1
}

pub type ChannelState {
ChannelState {
lazy_inbound_nonce: types.Nonce,
executable: Bool,
}
}

pub fn mark_verifiable(s: ChannelState, incoming: types.Nonce) -> ChannelState {
if nonce_next_ok(s.lazy_inbound_nonce, incoming) {
ChannelState {
lazy_inbound_nonce: s.lazy_inbound_nonce,
executable: True,
}
} else {
s
}
}

pub fn consume_executable_and_advance(s: ChannelState, incoming: types.Nonce) -> ChannelState {
let types.Nonce(c) = s.lazy_inbound_nonce
let types.Nonce(n) = incoming
if s.executable == True && n == c + 1 {
ChannelState {
lazy_inbound_nonce: types.Nonce(n),
executable: False,
}
} else {
s
}
}
Loading