Repository navigation
Expand file tree
/
Copy pathCrossLanguageVector.t.sol
More file actions
107 lines (93 loc) · 5.38 KB
/
Copy pathCrossLanguageVector.t.sol
File metadata and controls
107 lines (93 loc) · 5.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.24;
import {Test} from "./Std.sol";
import {ChannelRegistry, IRDecVerifier} from "../src/ChannelRegistry.sol";
/// THE PROOF (Phase 2c). A **real** signed channel state produced by the Rust
/// `tessera-channel` crate — minted from a fixed key in
/// `crates/tessera-channel/examples/eth_vector.rs` and pinned on the Rust side by
/// `crates/tessera-channel/tests/eth_vector.rs` — is hard-coded here and verified
/// against the contract's `ecrecover` path. This asserts the cross-language
/// crypto actually matches: same SHA-256 commitment, same keccak256 signed
/// digest, and `ecrecover` recovering the SAME Ethereum address Rust derived.
///
/// If this passes, a state signed in Rust is accepted on-chain unchanged.
/// If the encoding/digest ever drifts in either language, this test fails.
contract CrossLanguageVectorTest is Test {
ChannelRegistry reg;
// ===== Tessera channel cross-language signature vector =====
// Generated by `cargo run -p tessera-channel --example eth_vector`.
address constant USER_ADDR = 0x0c8DA9808f8b12E05404267590E1840C4902103B;
address constant RELAYER_ADDR = 0xCd1DAD2554B8059F6D2017227B7bd869823D14BF;
bytes32 constant CHAN_ID = 0x1111111111111111111111111111111111111111111111111111111111111111;
uint64 constant BALANCE = 600;
uint64 constant SEQ = 1;
bytes32 constant SALT = 0x2222222222222222222222222222222222222222222222222222222222222222;
uint256 constant B0 = 1000;
// SHA-256 state commitment S_i (internal to Rust):
bytes32 constant COMMITMENT =
0x2cd0dc6343230c6b74ba9ea24efd5ce61284f361adbdce3c693a67ee4fa7d0da;
// keccak256 chain-facing signed digest (what ecrecover consumes):
bytes32 constant DIGEST = 0x08228d974e2c0abb856aa502afd402633851c3cc0a6d1cf97fabeb914b3d99d3;
// user signature (r,s,v):
bytes32 constant USER_R = 0x526d85aa848f6e047052568b19943dcf23a4aff4b12c52044042f23767bf0cba;
bytes32 constant USER_S = 0x41e5f5c66e870d8d98164fa5eab6d97d405bde36004b3ed149bdbe500fd17963;
uint8 constant USER_V = 28;
// relayer co-signature (r,s,v):
bytes32 constant RELAYER_R = 0xbbe7591732280a0608363f12541baded329af28209344195fd72cd9652f8b261;
bytes32 constant RELAYER_S = 0x27983815abd630d28e7393643d9e28b6874240167f2bcf30854335ff6cdddb93;
uint8 constant RELAYER_V = 28;
// ===========================================================
function setUp() public {
reg = new ChannelRegistry(IRDecVerifier(address(0)));
}
function _vectorState() internal pure returns (ChannelRegistry.State memory) {
return ChannelRegistry.State({chanId: CHAN_ID, balance: BALANCE, seq: SEQ, salt: SALT});
}
/// (1) The Solidity SHA-256 commitment must equal the Rust commitment.
function testCommitmentMatchesRust() public view {
bytes32 c = reg.commitment(_vectorState());
assertEq(c, COMMITMENT, "SHA-256 commitment mismatch Rust<->Solidity");
}
/// (2) The Solidity keccak256 signed digest must equal the Rust digest.
function testDigestMatchesRust() public view {
bytes32 d = reg.stateDigest(_vectorState());
assertEq(d, DIGEST, "keccak256 state digest mismatch Rust<->Solidity");
}
/// (3) Raw `ecrecover` over the Rust digest recovers the Rust-derived USER
/// address — the irreducible cross-language crypto check.
function testEcrecoverRecoversUserAddress() public pure {
address recovered = ecrecover(DIGEST, USER_V, USER_R, USER_S);
assertEq(recovered, USER_ADDR, "ecrecover(user) != Rust user address");
}
/// (3b) ...and the RELAYER address from the co-signature.
function testEcrecoverRecoversRelayerAddress() public pure {
address recovered = ecrecover(DIGEST, RELAYER_V, RELAYER_R, RELAYER_S);
assertEq(recovered, RELAYER_ADDR, "ecrecover(relayer) != Rust relayer address");
}
/// (4) The contract's own verification path (`recoverSigner`, which
/// recomputes the digest from the State and enforces low-`s`) recovers
/// both addresses — i.e. a state signed in Rust is accepted on-chain.
function testContractRecoverSignerAcceptsRustState() public view {
ChannelRegistry.State memory s = _vectorState();
assertEq(reg.recoverSigner(s, USER_R, USER_S, USER_V), USER_ADDR, "recoverSigner(user)");
assertEq(
reg.recoverSigner(s, RELAYER_R, RELAYER_S, RELAYER_V),
RELAYER_ADDR,
"recoverSigner(relayer)"
);
}
/// (5) End-to-end: the Rust-signed doubly-signed state cooperatively closes a
/// real on-chain channel, paying the relayer `B0 - balance` and the user
/// `balance` — the whole cross-language path, funds and all.
function testRustStateCooperativelyClosesOnChain() public {
vm.deal(address(this), B0);
reg.open{value: B0}(CHAN_ID, USER_ADDR, RELAYER_ADDR, block.timestamp + 1 days);
uint256 userBefore = USER_ADDR.balance;
uint256 relayerBefore = RELAYER_ADDR.balance;
ChannelRegistry.State memory s = _vectorState();
reg.cooperativeClose(s, USER_R, USER_S, USER_V, RELAYER_R, RELAYER_S, RELAYER_V);
// relayer owed B0 - balance = 1000 - 600 = 400; user refunded 600.
assertEq(RELAYER_ADDR.balance - relayerBefore, B0 - BALANCE, "relayer payout");
assertEq(USER_ADDR.balance - userBefore, BALANCE, "user refund");
}
}