@mainsail/evm 0.0.1-evm.51 → 0.0.1-evm.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +5922 -0
- package/Cargo.toml +34 -0
- package/bindings/Cargo.toml +33 -0
- package/bindings/build.rs +6 -0
- package/bindings/src/ctx.rs +667 -0
- package/bindings/src/lib.rs +2231 -0
- package/bindings/src/logger.rs +110 -0
- package/bindings/src/result.rs +542 -0
- package/bindings/src/utils.rs +71 -0
- package/core/Cargo.toml +36 -0
- package/core/src/account.rs +112 -0
- package/core/src/bytecode.rs +39 -0
- package/core/src/compression.rs +158 -0
- package/core/src/db.rs +3311 -0
- package/core/src/events.rs +9 -0
- package/core/src/historical.rs +544 -0
- package/core/src/legacy.rs +153 -0
- package/core/src/lib.rs +14 -0
- package/core/src/logger.rs +98 -0
- package/core/src/logs_bloom.rs +96 -0
- package/core/src/precompiles.rs +450 -0
- package/core/src/receipt.rs +153 -0
- package/core/src/state_changes.rs +122 -0
- package/core/src/state_commit.rs +615 -0
- package/core/src/state_root.rs +266 -0
- package/index.d.ts +1 -0
- package/index.js +52 -52
- package/package.json +5 -4
- package/scripts/postinstall.mjs +73 -0
package/core/src/lib.rs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
pub mod account;
|
|
2
|
+
mod bytecode;
|
|
3
|
+
mod compression;
|
|
4
|
+
pub mod db;
|
|
5
|
+
mod events;
|
|
6
|
+
pub mod historical;
|
|
7
|
+
pub mod legacy;
|
|
8
|
+
pub mod logger;
|
|
9
|
+
pub mod logs_bloom;
|
|
10
|
+
pub mod precompiles;
|
|
11
|
+
pub mod receipt;
|
|
12
|
+
pub mod state_changes;
|
|
13
|
+
pub mod state_commit;
|
|
14
|
+
pub mod state_root;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
use std::sync::mpsc::Sender;
|
|
2
|
+
|
|
3
|
+
#[derive(Default, Clone)]
|
|
4
|
+
pub struct Logger {
|
|
5
|
+
// A channel is optional and if not present, log output is written via println!
|
|
6
|
+
pub sender: Option<Sender<(LogLevel, String)>>,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
impl Logger {
|
|
10
|
+
pub fn new(sender: Option<Sender<(LogLevel, String)>>) -> Self {
|
|
11
|
+
Self { sender }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pub fn log(&self, level: LogLevel, message: String) {
|
|
15
|
+
match &self.sender {
|
|
16
|
+
Some(sender) => {
|
|
17
|
+
if let Err(err) = sender.send((level, message)) {
|
|
18
|
+
eprintln!("failed to send log message: {}", err);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
None => {
|
|
22
|
+
println!("[{level}]: {message}");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#[derive(PartialEq, Eq)]
|
|
29
|
+
pub enum LogLevel {
|
|
30
|
+
Info,
|
|
31
|
+
Debug,
|
|
32
|
+
Notice,
|
|
33
|
+
Alert,
|
|
34
|
+
Warn,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
impl std::fmt::Display for LogLevel {
|
|
38
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
39
|
+
let uppercase = match self {
|
|
40
|
+
LogLevel::Info => "INFO",
|
|
41
|
+
LogLevel::Debug => "DEBUG",
|
|
42
|
+
LogLevel::Notice => "NOTICE",
|
|
43
|
+
LogLevel::Alert => "ALERT",
|
|
44
|
+
LogLevel::Warn => "WARN",
|
|
45
|
+
};
|
|
46
|
+
write!(f, "{}", uppercase)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#[cfg(test)]
|
|
51
|
+
mod tests {
|
|
52
|
+
use std::{sync::mpsc::channel, time::Duration};
|
|
53
|
+
|
|
54
|
+
use crate::logger::{LogLevel, Logger};
|
|
55
|
+
|
|
56
|
+
#[test]
|
|
57
|
+
fn test_logger_sends_log_message_through_channel() {
|
|
58
|
+
let (tx, rx) = channel();
|
|
59
|
+
let logger = Logger::new(Some(tx));
|
|
60
|
+
|
|
61
|
+
logger.log(LogLevel::Info, "hello world".to_string());
|
|
62
|
+
|
|
63
|
+
let (level, message) = rx.recv_timeout(Duration::from_millis(100)).unwrap();
|
|
64
|
+
assert_eq!(level.to_string(), "INFO");
|
|
65
|
+
assert_eq!(message, "hello world");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#[test]
|
|
69
|
+
fn test_logger_without_sender_does_not_panic() {
|
|
70
|
+
let logger = Logger::new(None);
|
|
71
|
+
|
|
72
|
+
logger.log(LogLevel::Warn, "printed to stdout".to_string());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#[test]
|
|
76
|
+
fn test_logger_with_disconnected_channel_does_not_panic() {
|
|
77
|
+
let (tx, rx) = channel();
|
|
78
|
+
drop(rx);
|
|
79
|
+
|
|
80
|
+
let logger = Logger::new(Some(tx));
|
|
81
|
+
logger.log(LogLevel::Alert, "this will fail to send".to_string());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#[test]
|
|
85
|
+
fn test_log_level_display_format() {
|
|
86
|
+
let cases = [
|
|
87
|
+
(LogLevel::Info, "INFO"),
|
|
88
|
+
(LogLevel::Debug, "DEBUG"),
|
|
89
|
+
(LogLevel::Notice, "NOTICE"),
|
|
90
|
+
(LogLevel::Alert, "ALERT"),
|
|
91
|
+
(LogLevel::Warn, "WARN"),
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
for (level, expected) in cases {
|
|
95
|
+
assert_eq!(level.to_string(), expected);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
use revm::primitives::alloy_primitives::Bloom;
|
|
2
|
+
|
|
3
|
+
use crate::db::PendingCommit;
|
|
4
|
+
|
|
5
|
+
pub fn calculate(pending_commit: &PendingCommit) -> Result<Bloom, crate::db::Error> {
|
|
6
|
+
let results = match pending_commit.built_commit.as_ref() {
|
|
7
|
+
Some(commit) => &commit.results,
|
|
8
|
+
None => &pending_commit.results,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let receipt_blooms = results
|
|
12
|
+
.values()
|
|
13
|
+
.map(|(r, _)| Bloom::from_iter(r.logs()))
|
|
14
|
+
.collect::<Vec<Bloom>>();
|
|
15
|
+
|
|
16
|
+
let logs_bloom = receipt_blooms
|
|
17
|
+
.into_iter()
|
|
18
|
+
.fold(Bloom::ZERO, |acc, value| acc | value);
|
|
19
|
+
|
|
20
|
+
Ok(logs_bloom)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#[test]
|
|
24
|
+
fn test_calculate_empty_logs_bloom() {
|
|
25
|
+
let result = calculate(&Default::default()).expect("ok");
|
|
26
|
+
assert_eq!(result, revm::primitives::alloy_primitives::bloom!());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#[test]
|
|
30
|
+
fn test_calculate_logs_bloom() {
|
|
31
|
+
let _logs =
|
|
32
|
+
|logs: Vec<revm::primitives::B256>| revm::context::result::ExecutionResult::Success {
|
|
33
|
+
logs: logs
|
|
34
|
+
.into_iter()
|
|
35
|
+
.map(|log| {
|
|
36
|
+
revm::primitives::Log::new_unchecked(
|
|
37
|
+
revm::primitives::address!("0000000000000000000000000000000000000000"),
|
|
38
|
+
vec![log],
|
|
39
|
+
Default::default(),
|
|
40
|
+
)
|
|
41
|
+
})
|
|
42
|
+
.collect::<Vec<revm::primitives::Log>>(),
|
|
43
|
+
gas: revm::context::result::ResultGas::default(),
|
|
44
|
+
output: revm::context::result::Output::Call(Default::default()),
|
|
45
|
+
reason: revm::context::result::SuccessReason::Stop,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
struct TestCase {
|
|
49
|
+
pub logs: Vec<revm::primitives::B256>,
|
|
50
|
+
pub bloom: Bloom,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for test_case in vec![
|
|
54
|
+
TestCase {
|
|
55
|
+
logs: vec![revm::primitives::b256!(
|
|
56
|
+
"02c69be41d0b7e40352fc85be1cd65eb03d40ef8427a0ca4596b1ead9a00e9fc"
|
|
57
|
+
)],
|
|
58
|
+
bloom: revm::primitives::alloy_primitives::bloom!(
|
|
59
|
+
"00000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002020000000000000000000000000000000000000000000000000000001000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
|
60
|
+
),
|
|
61
|
+
},
|
|
62
|
+
TestCase {
|
|
63
|
+
logs: vec![
|
|
64
|
+
revm::primitives::b256!(
|
|
65
|
+
"aacbdb204397aa18116c7df276b4d889c1232392d9538b0472f7d6e966b93bdd"
|
|
66
|
+
),
|
|
67
|
+
revm::primitives::b256!(
|
|
68
|
+
"c98570642b1c831b23513efccbd664243a6affe545e71afc30dc0b46670ecd49"
|
|
69
|
+
),
|
|
70
|
+
revm::primitives::b256!(
|
|
71
|
+
"263bc8782e51b510ea0d299830554278f9b864316f5e15280d54a47df679f337"
|
|
72
|
+
),
|
|
73
|
+
revm::primitives::b256!(
|
|
74
|
+
"a153068a03c13efdab230c32ca1b60220e723aa98077279e6c6a77df9b167951"
|
|
75
|
+
),
|
|
76
|
+
revm::primitives::b256!(
|
|
77
|
+
"ecec3851f00a82cc0ec13e3580c9ce19dbc5d58f45ec6ae252d84d1e341af8cb"
|
|
78
|
+
),
|
|
79
|
+
],
|
|
80
|
+
bloom: revm::primitives::alloy_primitives::bloom!(
|
|
81
|
+
"00040000000000000080000000000100000000000000040000000000000000000000000000000000000000000000000200000000000000040000000000000000000000000000000000000000000000000000000000000100000000000000000000000000002000000000000000000000000000000000000000010000000000000000000000000000000000000000000300000000000000000000000000010000000000000400000000000000000000000000000000000000000000000000000000010000000000000000000000000000000800000000000000008000000000000000000000000000000000000000000000000000000000000001000000008000"
|
|
82
|
+
),
|
|
83
|
+
},
|
|
84
|
+
] {
|
|
85
|
+
let mut pending = PendingCommit::default();
|
|
86
|
+
pending.results.insert(
|
|
87
|
+
revm::primitives::b256!(
|
|
88
|
+
"0000000000000000000000000000000000000000000000000000000000000001"
|
|
89
|
+
),
|
|
90
|
+
(_logs(test_case.logs), 0),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
let result = calculate(&pending).expect("ok");
|
|
94
|
+
assert_eq!(result, test_case.bloom);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
use blst::BLST_ERROR;
|
|
2
|
+
use blst::min_pk::{PublicKey, Signature};
|
|
3
|
+
|
|
4
|
+
use revm::context::Cfg;
|
|
5
|
+
use revm::context_interface::ContextTr;
|
|
6
|
+
use revm::handler::{EthPrecompiles, PrecompileProvider, precompile_output_to_interpreter_result};
|
|
7
|
+
use revm::interpreter::{CallInputs, InterpreterResult};
|
|
8
|
+
use revm::precompile::{PrecompileHalt, PrecompileOutput, PrecompileResult};
|
|
9
|
+
use revm::primitives::hardfork::SpecId;
|
|
10
|
+
use revm::primitives::{Address, AddressSet, Bytes, address};
|
|
11
|
+
|
|
12
|
+
pub const BLS_POP_VERIFY_ADDR: Address = address!("0000000000000000000000000000000001181200");
|
|
13
|
+
|
|
14
|
+
pub struct MainsailPrecompiles {
|
|
15
|
+
eth: EthPrecompiles,
|
|
16
|
+
warm_addresses: AddressSet,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
impl MainsailPrecompiles {
|
|
20
|
+
pub fn new(spec: SpecId) -> Self {
|
|
21
|
+
let eth = EthPrecompiles::new(spec);
|
|
22
|
+
|
|
23
|
+
let mut warm_addresses = eth.warm_addresses().clone();
|
|
24
|
+
warm_addresses.insert(BLS_POP_VERIFY_ADDR);
|
|
25
|
+
|
|
26
|
+
Self {
|
|
27
|
+
eth,
|
|
28
|
+
warm_addresses,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
impl<CTX: ContextTr> PrecompileProvider<CTX> for MainsailPrecompiles {
|
|
34
|
+
type Output = InterpreterResult;
|
|
35
|
+
|
|
36
|
+
fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool {
|
|
37
|
+
PrecompileProvider::<CTX>::set_spec(&mut self.eth, spec)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fn run(
|
|
41
|
+
&mut self,
|
|
42
|
+
context: &mut CTX,
|
|
43
|
+
inputs: &CallInputs,
|
|
44
|
+
) -> Result<Option<InterpreterResult>, String> {
|
|
45
|
+
if inputs.bytecode_address == BLS_POP_VERIFY_ADDR {
|
|
46
|
+
let input_bytes = inputs.input.as_bytes(context);
|
|
47
|
+
let output = bls_pop_verify(&input_bytes, inputs.gas_limit, inputs.reservoir)
|
|
48
|
+
.map_err(|e| e.to_string())?;
|
|
49
|
+
let result = precompile_output_to_interpreter_result(output, inputs.gas_limit);
|
|
50
|
+
return Ok(Some(result));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Fall through to the standard mainnet precompiles for everything else.
|
|
54
|
+
PrecompileProvider::<CTX>::run(&mut self.eth, context, inputs)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fn warm_addresses(&self) -> &AddressSet {
|
|
58
|
+
&self.warm_addresses
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fn contains(&self, address: &Address) -> bool {
|
|
62
|
+
*address == BLS_POP_VERIFY_ADDR || self.eth.contains(address)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// BLS12-381 proof-of-possession verifier under the POP scheme of
|
|
67
|
+
/// draft-irtf-cfrg-bls-signature-05 §4.2.3.
|
|
68
|
+
///
|
|
69
|
+
/// Input (144 B): 48-byte compressed G1 public key || 96-byte compressed G2 signature
|
|
70
|
+
/// Output (32 B): 0x00..01 if the PoP is valid, 0x00..00 otherwise.
|
|
71
|
+
///
|
|
72
|
+
/// Structural failures (wrong length, malformed point encoding, subgroup-check
|
|
73
|
+
/// failure) HALT the precompile, consuming `gas_limit` — this is intentional to
|
|
74
|
+
/// discourage spam with junk inputs and matches how the EIP-2537 precompiles
|
|
75
|
+
/// signal the same conditions. A *well-formed* but cryptographically invalid
|
|
76
|
+
/// PoP returns 0x00..00 at the flat `POP_VERIFY_GAS` cost.
|
|
77
|
+
const POP_DST: &[u8] = b"BLS_POP_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_";
|
|
78
|
+
const POP_VERIFY_GAS: u64 = 150_000;
|
|
79
|
+
const PK_LEN: usize = 48;
|
|
80
|
+
const POP_LEN: usize = 96;
|
|
81
|
+
const INPUT_LEN: usize = PK_LEN + POP_LEN;
|
|
82
|
+
|
|
83
|
+
fn bls_pop_verify(input: &[u8], gas_limit: u64, reservoir: u64) -> PrecompileResult {
|
|
84
|
+
if gas_limit < POP_VERIFY_GAS {
|
|
85
|
+
return Ok(PrecompileOutput::halt(PrecompileHalt::OutOfGas, reservoir));
|
|
86
|
+
}
|
|
87
|
+
if input.len() != INPUT_LEN {
|
|
88
|
+
return Ok(PrecompileOutput::halt(
|
|
89
|
+
PrecompileHalt::other_static("bls_pop: bad input length"),
|
|
90
|
+
reservoir,
|
|
91
|
+
));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let pk_bytes = &input[..PK_LEN];
|
|
95
|
+
let pop_bytes = &input[PK_LEN..];
|
|
96
|
+
|
|
97
|
+
let pk = match PublicKey::key_validate(pk_bytes) {
|
|
98
|
+
Ok(p) => p,
|
|
99
|
+
Err(_) => {
|
|
100
|
+
return Ok(PrecompileOutput::halt(
|
|
101
|
+
PrecompileHalt::Bls12381G1NotInSubgroup,
|
|
102
|
+
reservoir,
|
|
103
|
+
));
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
let sig = match Signature::sig_validate(pop_bytes, true) {
|
|
108
|
+
Ok(s) => s,
|
|
109
|
+
Err(_) => {
|
|
110
|
+
return Ok(PrecompileOutput::halt(
|
|
111
|
+
PrecompileHalt::Bls12381G2NotInSubgroup,
|
|
112
|
+
reservoir,
|
|
113
|
+
));
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
let res = sig.verify(
|
|
118
|
+
false, // already subgroup/infinity checked via sig_validate(...)
|
|
119
|
+
pk_bytes, // PoP message is the compressed public key bytes
|
|
120
|
+
POP_DST,
|
|
121
|
+
&[],
|
|
122
|
+
&pk,
|
|
123
|
+
false, // already key-validated via key_validate(...)
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
let mut out = [0u8; 32];
|
|
127
|
+
if res == BLST_ERROR::BLST_SUCCESS {
|
|
128
|
+
out[31] = 1;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
Ok(PrecompileOutput::new(
|
|
132
|
+
POP_VERIFY_GAS,
|
|
133
|
+
Bytes::from(out.to_vec()),
|
|
134
|
+
reservoir,
|
|
135
|
+
))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#[cfg(test)]
|
|
139
|
+
mod tests {
|
|
140
|
+
use blst::min_pk::SecretKey;
|
|
141
|
+
use revm::precompile::{PrecompileHalt, PrecompileOutput, PrecompileStatus};
|
|
142
|
+
|
|
143
|
+
use crate::precompiles::{POP_DST, POP_VERIFY_GAS, bls_pop_verify};
|
|
144
|
+
|
|
145
|
+
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
/// Deterministic keygen for reproducible tests. The seed byte distinguishes
|
|
148
|
+
/// independent key pairs without bringing in a CSPRNG.
|
|
149
|
+
fn keygen(seed: u8) -> (SecretKey, Vec<u8>) {
|
|
150
|
+
let mut ikm = [0u8; 32];
|
|
151
|
+
ikm[0] = seed;
|
|
152
|
+
for i in 1..32 {
|
|
153
|
+
ikm[i] = seed.wrapping_mul(i as u8).wrapping_add(0xa5);
|
|
154
|
+
}
|
|
155
|
+
let sk = SecretKey::key_gen(&ikm, &[]).expect("keygen");
|
|
156
|
+
let pk_bytes = sk.sk_to_pk().compress().to_vec();
|
|
157
|
+
(sk, pk_bytes)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/// Produce a valid PoP: sign pk_bytes under POP_DST, return the 96-byte
|
|
161
|
+
/// compressed G2 signature.
|
|
162
|
+
fn sign_pop(sk: &SecretKey, pk_bytes: &[u8]) -> Vec<u8> {
|
|
163
|
+
sk.sign(pk_bytes, POP_DST, &[]).compress().to_vec()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fn build_input(pk: &[u8], pop: &[u8]) -> Vec<u8> {
|
|
167
|
+
let mut v = Vec::with_capacity(pk.len() + pop.len());
|
|
168
|
+
v.extend_from_slice(pk);
|
|
169
|
+
v.extend_from_slice(pop);
|
|
170
|
+
v
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// Assert the precompile returned 32 bytes of 0x..01 (valid PoP).
|
|
174
|
+
fn assert_valid(out: &PrecompileOutput) {
|
|
175
|
+
assert_eq!(out.status, PrecompileStatus::Success, "expected Success");
|
|
176
|
+
assert_eq!(out.bytes.len(), 32);
|
|
177
|
+
assert_eq!(out.bytes[..31], [0u8; 31][..]);
|
|
178
|
+
assert_eq!(out.bytes[31], 0x01, "expected last byte = 0x01");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/// Assert the precompile returned 32 bytes of zero (well-formed input, sig invalid).
|
|
182
|
+
fn assert_invalid(out: &PrecompileOutput) {
|
|
183
|
+
assert_eq!(out.status, PrecompileStatus::Success, "expected Success");
|
|
184
|
+
assert_eq!(out.bytes.len(), 32);
|
|
185
|
+
assert_eq!(out.bytes[..], [0u8; 32][..], "expected all-zero output");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/// Assert the precompile halted with the specific reason.
|
|
189
|
+
fn assert_halt(out: &PrecompileOutput, expected: &PrecompileHalt) {
|
|
190
|
+
match &out.status {
|
|
191
|
+
PrecompileStatus::Halt(reason) => assert_eq!(reason, expected),
|
|
192
|
+
other => panic!("expected Halt({:?}), got {:?}", expected, other),
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const VALID_INPUT_LEN: usize = 48 + 96;
|
|
197
|
+
|
|
198
|
+
// ── Happy path ─────────────────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
#[test]
|
|
201
|
+
fn round_trip_valid_pop() {
|
|
202
|
+
let (sk, pk) = keygen(1);
|
|
203
|
+
let pop = sign_pop(&sk, &pk);
|
|
204
|
+
let input = build_input(&pk, &pop);
|
|
205
|
+
|
|
206
|
+
let out = bls_pop_verify(&input, POP_VERIFY_GAS, 0).expect("Ok");
|
|
207
|
+
assert_valid(&out);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
#[test]
|
|
211
|
+
fn round_trip_many_keys() {
|
|
212
|
+
// Sweep a handful of distinct keys to catch any accidental coupling.
|
|
213
|
+
for seed in 1u8..=8 {
|
|
214
|
+
let (sk, pk) = keygen(seed);
|
|
215
|
+
let pop = sign_pop(&sk, &pk);
|
|
216
|
+
let input = build_input(&pk, &pop);
|
|
217
|
+
|
|
218
|
+
let out = bls_pop_verify(&input, POP_VERIFY_GAS, 0).expect("Ok");
|
|
219
|
+
assert_valid(&out);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ── Gas accounting ─────────────────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
#[test]
|
|
226
|
+
fn out_of_gas() {
|
|
227
|
+
let (sk, pk) = keygen(1);
|
|
228
|
+
let pop = sign_pop(&sk, &pk);
|
|
229
|
+
let input = build_input(&pk, &pop);
|
|
230
|
+
|
|
231
|
+
let out = bls_pop_verify(&input, POP_VERIFY_GAS - 1, 0).expect("Ok");
|
|
232
|
+
assert_halt(&out, &PrecompileHalt::OutOfGas);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
#[test]
|
|
236
|
+
fn exact_gas_succeeds() {
|
|
237
|
+
let (sk, pk) = keygen(1);
|
|
238
|
+
let pop = sign_pop(&sk, &pk);
|
|
239
|
+
let input = build_input(&pk, &pop);
|
|
240
|
+
|
|
241
|
+
let out = bls_pop_verify(&input, POP_VERIFY_GAS, 0).expect("Ok");
|
|
242
|
+
assert_valid(&out);
|
|
243
|
+
// Successful path bills the full POP_VERIFY_GAS.
|
|
244
|
+
assert_eq!(out.gas_used, POP_VERIFY_GAS);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ── Input length guards ────────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
#[test]
|
|
250
|
+
fn input_empty() {
|
|
251
|
+
let out = bls_pop_verify(&[], POP_VERIFY_GAS, 0).expect("Ok");
|
|
252
|
+
assert!(matches!(out.status, PrecompileStatus::Halt(_)));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
#[test]
|
|
256
|
+
fn input_one_byte_short() {
|
|
257
|
+
let input = vec![0u8; VALID_INPUT_LEN - 1];
|
|
258
|
+
let out = bls_pop_verify(&input, POP_VERIFY_GAS, 0).expect("Ok");
|
|
259
|
+
assert!(matches!(out.status, PrecompileStatus::Halt(_)));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
#[test]
|
|
263
|
+
fn input_one_byte_long() {
|
|
264
|
+
let input = vec![0u8; VALID_INPUT_LEN + 1];
|
|
265
|
+
let out = bls_pop_verify(&input, POP_VERIFY_GAS, 0).expect("Ok");
|
|
266
|
+
assert!(matches!(out.status, PrecompileStatus::Halt(_)));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ── Malformed public key (G1) ──────────────────────────────────────────
|
|
270
|
+
|
|
271
|
+
#[test]
|
|
272
|
+
fn pk_garbage_not_on_curve() {
|
|
273
|
+
// 48 random-looking bytes — vanishingly unlikely to decode to a valid G1 point.
|
|
274
|
+
let pk = [0xff; 48];
|
|
275
|
+
let (sk, real_pk) = keygen(1);
|
|
276
|
+
let pop = sign_pop(&sk, &real_pk);
|
|
277
|
+
let input = build_input(&pk, &pop);
|
|
278
|
+
|
|
279
|
+
let out = bls_pop_verify(&input, POP_VERIFY_GAS, 0).expect("Ok");
|
|
280
|
+
assert_halt(&out, &PrecompileHalt::Bls12381G1NotInSubgroup);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
#[test]
|
|
284
|
+
fn pk_infinity_rejected_by_validate() {
|
|
285
|
+
// Compressed G1 point-at-infinity: byte 0 has compression bit (0x80) and
|
|
286
|
+
// infinity bit (0x40) set; everything else zero.
|
|
287
|
+
let mut pk = vec![0u8; 48];
|
|
288
|
+
pk[0] = 0xc0;
|
|
289
|
+
let (sk, real_pk) = keygen(1);
|
|
290
|
+
let pop = sign_pop(&sk, &real_pk);
|
|
291
|
+
let input = build_input(&pk, &pop);
|
|
292
|
+
|
|
293
|
+
let out = bls_pop_verify(&input, POP_VERIFY_GAS, 0).expect("Ok");
|
|
294
|
+
// from_bytes accepts infinity; validate() rejects it (KeyValidate forbids 1_G1).
|
|
295
|
+
assert_halt(&out, &PrecompileHalt::Bls12381G1NotInSubgroup);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
#[test]
|
|
299
|
+
fn pk_uncompressed_encoding_rejected() {
|
|
300
|
+
// Uncompressed encoding has the compression bit unset → from_bytes rejects.
|
|
301
|
+
let mut pk = vec![0u8; 48];
|
|
302
|
+
pk[0] = 0x00;
|
|
303
|
+
let (sk, real_pk) = keygen(1);
|
|
304
|
+
let pop = sign_pop(&sk, &real_pk);
|
|
305
|
+
let input = build_input(&pk, &pop);
|
|
306
|
+
|
|
307
|
+
let out = bls_pop_verify(&input, POP_VERIFY_GAS, 0).expect("Ok");
|
|
308
|
+
assert_halt(&out, &PrecompileHalt::Bls12381G1NotInSubgroup);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ── Malformed signature (G2) ───────────────────────────────────────────
|
|
312
|
+
|
|
313
|
+
#[test]
|
|
314
|
+
fn sig_garbage_not_on_curve() {
|
|
315
|
+
let (_, pk) = keygen(1);
|
|
316
|
+
let pop = [0xff; 96];
|
|
317
|
+
let input = build_input(&pk, &pop);
|
|
318
|
+
|
|
319
|
+
let out = bls_pop_verify(&input, POP_VERIFY_GAS, 0).expect("Ok");
|
|
320
|
+
assert_halt(&out, &PrecompileHalt::Bls12381G2NotInSubgroup);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
#[test]
|
|
324
|
+
fn sig_infinity_rejected_by_validate() {
|
|
325
|
+
let (_, pk) = keygen(1);
|
|
326
|
+
// Compressed G2 point-at-infinity: 0xc0 || 95 × 0x00.
|
|
327
|
+
let mut pop = vec![0u8; 96];
|
|
328
|
+
pop[0] = 0xc0;
|
|
329
|
+
let input = build_input(&pk, &pop);
|
|
330
|
+
|
|
331
|
+
let out = bls_pop_verify(&input, POP_VERIFY_GAS, 0).expect("Ok");
|
|
332
|
+
assert_halt(&out, &PrecompileHalt::Bls12381G2NotInSubgroup);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ── Cryptographic failures — well-formed, just wrong ───────────────────
|
|
336
|
+
|
|
337
|
+
#[test]
|
|
338
|
+
fn wrong_dst_used_for_signing() {
|
|
339
|
+
// Sign pk_bytes under the SIG DST, then verify under POP DST.
|
|
340
|
+
// This is the regression test you want if anyone ever copies the DST
|
|
341
|
+
// constant from @chainsafe/bls (which uses SIG_DST).
|
|
342
|
+
let (sk, pk) = keygen(1);
|
|
343
|
+
let sig_dst: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_";
|
|
344
|
+
let pop = sk.sign(&pk, sig_dst, &[]).compress().to_vec();
|
|
345
|
+
let input = build_input(&pk, &pop);
|
|
346
|
+
|
|
347
|
+
let out = bls_pop_verify(&input, POP_VERIFY_GAS, 0).expect("Ok");
|
|
348
|
+
assert_invalid(&out);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
#[test]
|
|
352
|
+
fn wrong_message_signed() {
|
|
353
|
+
// Sign 48 bytes of zeros under POP_DST; submit (real_pk, sig).
|
|
354
|
+
// The sig is valid under POP_DST, just for the wrong message.
|
|
355
|
+
let (sk, pk) = keygen(1);
|
|
356
|
+
let zeros = [0u8; 48];
|
|
357
|
+
let pop = sk.sign(&zeros, POP_DST, &[]).compress().to_vec();
|
|
358
|
+
let input = build_input(&pk, &pop);
|
|
359
|
+
|
|
360
|
+
let out = bls_pop_verify(&input, POP_VERIFY_GAS, 0).expect("Ok");
|
|
361
|
+
assert_invalid(&out);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
#[test]
|
|
365
|
+
fn pk_substituted_at_input() {
|
|
366
|
+
// Build a valid (pk1, pop1) pair, then submit (pk2, pop1).
|
|
367
|
+
let (sk1, pk1) = keygen(1);
|
|
368
|
+
let (_sk2, pk2) = keygen(2);
|
|
369
|
+
let pop1 = sign_pop(&sk1, &pk1);
|
|
370
|
+
let input = build_input(&pk2, &pop1);
|
|
371
|
+
|
|
372
|
+
let out = bls_pop_verify(&input, POP_VERIFY_GAS, 0).expect("Ok");
|
|
373
|
+
assert_invalid(&out);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
#[test]
|
|
377
|
+
fn pop_signed_by_different_key() {
|
|
378
|
+
// sk1 signs pk2_bytes under POP_DST; submit (pk2, sig). Verify against pk2
|
|
379
|
+
// must fail because the sig is from sk1.
|
|
380
|
+
let (sk1, _pk1) = keygen(1);
|
|
381
|
+
let (_sk2, pk2) = keygen(2);
|
|
382
|
+
let pop = sk1.sign(&pk2, POP_DST, &[]).compress().to_vec();
|
|
383
|
+
let input = build_input(&pk2, &pop);
|
|
384
|
+
|
|
385
|
+
let out = bls_pop_verify(&input, POP_VERIFY_GAS, 0).expect("Ok");
|
|
386
|
+
assert_invalid(&out);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
#[test]
|
|
390
|
+
fn pop_reuse_across_keys_fails() {
|
|
391
|
+
// The textbook PoP property: a PoP for pk1 is not a PoP for pk2.
|
|
392
|
+
// Even if both are produced by honest signers, you can't claim one as the other.
|
|
393
|
+
let (sk1, pk1) = keygen(1);
|
|
394
|
+
let (_sk2, pk2) = keygen(2);
|
|
395
|
+
let pop_for_pk1 = sign_pop(&sk1, &pk1);
|
|
396
|
+
let input = build_input(&pk2, &pop_for_pk1);
|
|
397
|
+
|
|
398
|
+
let out = bls_pop_verify(&input, POP_VERIFY_GAS, 0).expect("Ok");
|
|
399
|
+
assert_invalid(&out);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
#[test]
|
|
403
|
+
fn tampered_signature_last_byte_fails() {
|
|
404
|
+
let (sk, pk) = keygen(1);
|
|
405
|
+
let mut pop = sign_pop(&sk, &pk);
|
|
406
|
+
pop[95] ^= 1; // flip the lowest bit
|
|
407
|
+
let input = build_input(&pk, &pop);
|
|
408
|
+
|
|
409
|
+
let out = bls_pop_verify(&input, POP_VERIFY_GAS, 0).expect("Ok");
|
|
410
|
+
// Either the modified point is off-curve (halt) or on-curve but invalid (0x..00).
|
|
411
|
+
// Both outcomes are acceptable; what matters is it's not 0x..01.
|
|
412
|
+
match out.status {
|
|
413
|
+
PrecompileStatus::Success => assert_invalid(&out),
|
|
414
|
+
PrecompileStatus::Halt(_) => {}
|
|
415
|
+
other => panic!("unexpected status: {:?}", other),
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
#[test]
|
|
420
|
+
fn tampered_pk_low_bit_fails() {
|
|
421
|
+
let (sk, real_pk) = keygen(1);
|
|
422
|
+
let pop = sign_pop(&sk, &real_pk);
|
|
423
|
+
let mut pk = real_pk.clone();
|
|
424
|
+
pk[47] ^= 1;
|
|
425
|
+
let input = build_input(&pk, &pop);
|
|
426
|
+
|
|
427
|
+
let out = bls_pop_verify(&input, POP_VERIFY_GAS, 0).expect("Ok");
|
|
428
|
+
match out.status {
|
|
429
|
+
PrecompileStatus::Success => assert_invalid(&out),
|
|
430
|
+
PrecompileStatus::Halt(_) => {}
|
|
431
|
+
other => panic!("unexpected status: {:?}", other),
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
#[test]
|
|
436
|
+
fn attacker_cannot_self_pop_a_key_they_dont_control() {
|
|
437
|
+
// Take a public key we know the secret for, and try to PoP it by signing with a *different* key.
|
|
438
|
+
// The PoP must fail. This is the load-bearing property of the POP scheme.
|
|
439
|
+
let (sk_attacker, _) = keygen(99);
|
|
440
|
+
let (_, victim_pk) = keygen(2);
|
|
441
|
+
let attempt = sk_attacker
|
|
442
|
+
.sign(&victim_pk, POP_DST, &[])
|
|
443
|
+
.compress()
|
|
444
|
+
.to_vec();
|
|
445
|
+
let input = build_input(&victim_pk, &attempt);
|
|
446
|
+
|
|
447
|
+
let out = bls_pop_verify(&input, POP_VERIFY_GAS, 0).expect("Ok");
|
|
448
|
+
assert_invalid(&out);
|
|
449
|
+
}
|
|
450
|
+
}
|