diff --git a/.gas-snapshot b/.gas-snapshot index e41656f8..7f64b9ca 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,17 +1,17 @@ KernelECDSATest:test_default_validator_disable() (gas: 0) -KernelECDSATest:test_default_validator_disable() (gas: 180311) +KernelECDSATest:test_default_validator_disable() (gas: 180648) KernelECDSATest:test_default_validator_enable() (gas: 0) -KernelECDSATest:test_default_validator_enable() (gas: 187800) -KernelECDSATest:test_disable_mode() (gas: 182795) -KernelECDSATest:test_disable_mode() (gas: 192079) +KernelECDSATest:test_default_validator_enable() (gas: 188140) +KernelECDSATest:test_disable_mode() (gas: 183150) +KernelECDSATest:test_disable_mode() (gas: 192689) KernelECDSATest:test_eip712() (gas: 15684) KernelECDSATest:test_eip712() (gas: 15684) -KernelECDSATest:test_enable_then_mode_1() (gas: 277994) -KernelECDSATest:test_enable_then_mode_1() (gas: 286106) -KernelECDSATest:test_external_call_batch_execute_fail() (gas: 23628) -KernelECDSATest:test_external_call_batch_execute_fail() (gas: 29534) -KernelECDSATest:test_external_call_batch_execute_success() (gas: 20629) -KernelECDSATest:test_external_call_batch_execute_success() (gas: 26536) +KernelECDSATest:test_enable_then_mode_1() (gas: 279597) +KernelECDSATest:test_enable_then_mode_1() (gas: 287402) +KernelECDSATest:test_external_call_batch_execute_fail() (gas: 23625) +KernelECDSATest:test_external_call_batch_execute_fail() (gas: 29531) +KernelECDSATest:test_external_call_batch_execute_success() (gas: 20626) +KernelECDSATest:test_external_call_batch_execute_success() (gas: 26533) KernelECDSATest:test_external_call_default() (gas: 23511) KernelECDSATest:test_external_call_default() (gas: 29338) KernelECDSATest:test_external_call_execute_delegatecall_fail() (gas: 22499) @@ -24,137 +24,248 @@ KernelECDSATest:test_external_call_execute_fail() (gas: 21968) KernelECDSATest:test_external_call_execute_fail() (gas: 27862) KernelECDSATest:test_external_call_execute_success() (gas: 20070) KernelECDSATest:test_external_call_execute_success() (gas: 25987) -KernelECDSATest:test_external_call_execution() (gas: 497819) -KernelECDSATest:test_external_call_execution() (gas: 510696) +KernelECDSATest:test_external_call_execution() (gas: 498410) +KernelECDSATest:test_external_call_execution() (gas: 511032) KernelECDSATest:test_fail_validate_not_activate() (gas: 0) -KernelECDSATest:test_fail_validate_not_activate() (gas: 303491) -KernelECDSATest:test_fail_validate_wrongsignature() (gas: 24904) -KernelECDSATest:test_fail_validate_wrongsignature() (gas: 34133) +KernelECDSATest:test_fail_validate_not_activate() (gas: 303494) +KernelECDSATest:test_fail_validate_wrongsignature() (gas: 25013) +KernelECDSATest:test_fail_validate_wrongsignature() (gas: 34351) KernelECDSATest:test_get_nonce() (gas: 25179) KernelECDSATest:test_get_nonce() (gas: 25201) KernelECDSATest:test_get_nonce(uint192) (runs: 1024, μ: 19842, ~: 19842) KernelECDSATest:test_get_nonce(uint192) (runs: 1024, μ: 19842, ~: 19842) KernelECDSATest:test_ignore() (gas: 868) -KernelECDSATest:test_initialize() (gas: 126784) +KernelECDSATest:test_initialize() (gas: 126488) KernelECDSATest:test_initialize() (gas: 154121) KernelECDSATest:test_initialize_twice() (gas: 18555) -KernelECDSATest:test_initialize_twice() (gas: 18698) -KernelECDSATest:test_mode_2() (gas: 243052) -KernelECDSATest:test_mode_2() (gas: 250243) -KernelECDSATest:test_revert_when_mode_disabled() (gas: 199003) -KernelECDSATest:test_revert_when_mode_disabled() (gas: 208287) -KernelECDSATest:test_set_default_validator() (gas: 417145) -KernelECDSATest:test_set_default_validator() (gas: 423335) -KernelECDSATest:test_set_execution() (gas: 458142) -KernelECDSATest:test_set_execution() (gas: 468303) +KernelECDSATest:test_initialize_twice() (gas: 18599) +KernelECDSATest:test_mode_2() (gas: 243897) +KernelECDSATest:test_mode_2() (gas: 251079) +KernelECDSATest:test_revert_when_mode_disabled() (gas: 200119) +KernelECDSATest:test_revert_when_mode_disabled() (gas: 209710) +KernelECDSATest:test_set_default_validator() (gas: 417747) +KernelECDSATest:test_set_default_validator() (gas: 423674) +KernelECDSATest:test_set_execution() (gas: 458742) +KernelECDSATest:test_set_execution() (gas: 468648) KernelECDSATest:test_should_emit_event_on_receive() (gas: 23220) KernelECDSATest:test_should_emit_event_on_receive() (gas: 23220) -KernelECDSATest:test_should_receive_erc1155() (gas: 701145) -KernelECDSATest:test_should_receive_erc1155() (gas: 701171) -KernelECDSATest:test_should_receive_erc1155_batch() (gas: 728411) -KernelECDSATest:test_should_receive_erc1155_batch() (gas: 728411) -KernelECDSATest:test_should_receive_erc721() (gas: 597680) -KernelECDSATest:test_should_receive_erc721() (gas: 597702) +KernelECDSATest:test_should_receive_erc1155() (gas: 700299) +KernelECDSATest:test_should_receive_erc1155() (gas: 700325) +KernelECDSATest:test_should_receive_erc1155_batch() (gas: 727496) +KernelECDSATest:test_should_receive_erc1155_batch() (gas: 727496) +KernelECDSATest:test_should_receive_erc721() (gas: 593075) +KernelECDSATest:test_should_receive_erc721() (gas: 593097) KernelECDSATest:test_should_return_address_if_deployed() (gas: 21970) KernelECDSATest:test_should_return_address_if_deployed() (gas: 21976) -KernelECDSATest:test_sudo() (gas: 170002) -KernelECDSATest:test_sudo() (gas: 179450) -KernelECDSATest:test_sudo_wrongSig() (gas: 102566) -KernelECDSATest:test_sudo_wrongSig() (gas: 115709) -KernelECDSATest:test_transfer_ownership() (gas: 178398) +KernelECDSATest:test_sudo() (gas: 170590) +KernelECDSATest:test_sudo() (gas: 179783) +KernelECDSATest:test_sudo_wrongSig() (gas: 103121) +KernelECDSATest:test_sudo_wrongSig() (gas: 116321) +KernelECDSATest:test_transfer_ownership() (gas: 185003) KernelECDSATest:test_upgrade() (gas: 21292) KernelECDSATest:test_upgrade() (gas: 21292) -KernelECDSATest:test_validateUserOp_fail_invalid_mode() (gas: 26550) -KernelECDSATest:test_validateUserOp_fail_invalid_mode() (gas: 26550) -KernelECDSATest:test_validateUserOp_fail_not_entryPoint() (gas: 30591) -KernelECDSATest:test_validateUserOp_fail_not_entryPoint() (gas: 30634) -KernelECDSATest:test_validate_signature() (gas: 155714) -KernelECDSATest:test_validate_signature() (gas: 194502) -KernelHelperTest:testIntersect(uint48,uint48,uint48,uint48) (runs: 1024, μ: 871, ~: 869) -KillSwitchValidatorTest:test_default_validator_disable() (gas: 180432) -KillSwitchValidatorTest:test_default_validator_enable() (gas: 187921) -KillSwitchValidatorTest:test_disable_mode() (gas: 182916) +KernelECDSATest:test_validateUserOp_fail_invalid_mode() (gas: 26934) +KernelECDSATest:test_validateUserOp_fail_invalid_mode() (gas: 26986) +KernelECDSATest:test_validateUserOp_fail_not_entryPoint() (gas: 23364) +KernelECDSATest:test_validateUserOp_fail_not_entryPoint() (gas: 23416) +KernelECDSATest:test_validate_signature() (gas: 155870) +KernelECDSATest:test_validate_signature() (gas: 195172) +KernelECDSATypedTest:test_default_validator_disable() (gas: 185046) +KernelECDSATypedTest:test_default_validator_enable() (gas: 192011) +KernelECDSATypedTest:test_disable_mode() (gas: 187373) +KernelECDSATypedTest:test_eip712() (gas: 15684) +KernelECDSATypedTest:test_enable_then_mode_1() (gas: 291627) +KernelECDSATypedTest:test_external_call_batch_execute_fail() (gas: 29575) +KernelECDSATypedTest:test_external_call_batch_execute_success() (gas: 26577) +KernelECDSATypedTest:test_external_call_default() (gas: 29382) +KernelECDSATypedTest:test_external_call_execute_delegatecall_fail() (gas: 28425) +KernelECDSATypedTest:test_external_call_execute_delegatecall_option_fail() (gas: 26373) +KernelECDSATypedTest:test_external_call_execute_delegatecall_success() (gas: 25665) +KernelECDSATypedTest:test_external_call_execute_fail() (gas: 27906) +KernelECDSATypedTest:test_external_call_execute_success() (gas: 26031) +KernelECDSATypedTest:test_external_call_execution() (gas: 515344) +KernelECDSATypedTest:test_fail_validate_not_activate() (gas: 303494) +KernelECDSATypedTest:test_fail_validate_wrongsignature() (gas: 36477) +KernelECDSATypedTest:test_get_nonce() (gas: 25179) +KernelECDSATypedTest:test_get_nonce(uint192) (runs: 1024, μ: 19842, ~: 19842) +KernelECDSATypedTest:test_ignore() (gas: 868) +KernelECDSATypedTest:test_initialize() (gas: 153627) +KernelECDSATypedTest:test_initialize_twice() (gas: 18555) +KernelECDSATypedTest:test_mode_2() (gas: 253336) +KernelECDSATypedTest:test_revert_when_mode_disabled() (gas: 204271) +KernelECDSATypedTest:test_set_default_validator() (gas: 427897) +KernelECDSATypedTest:test_set_execution() (gas: 472872) +KernelECDSATypedTest:test_should_emit_event_on_receive() (gas: 23220) +KernelECDSATypedTest:test_should_receive_erc1155() (gas: 700299) +KernelECDSATypedTest:test_should_receive_erc1155_batch() (gas: 727496) +KernelECDSATypedTest:test_should_receive_erc721() (gas: 593097) +KernelECDSATypedTest:test_should_return_address_if_deployed() (gas: 21976) +KernelECDSATypedTest:test_sudo() (gas: 184006) +KernelECDSATypedTest:test_sudo_wrongSig() (gas: 116744) +KernelECDSATypedTest:test_upgrade() (gas: 21292) +KernelECDSATypedTest:test_validateUserOp_fail_invalid_mode() (gas: 26934) +KernelECDSATypedTest:test_validateUserOp_fail_not_entryPoint() (gas: 23364) +KernelECDSATypedTest:test_validate_signature() (gas: 191595) +KernelHelperTest:testIntersect(uint48,uint48,uint48,uint48) (runs: 1024, μ: 986, ~: 991) +KernelWeightedECDSATest:test_default_validator_disable() (gas: 305435) +KernelWeightedECDSATest:test_default_validator_enable() (gas: 861) +KernelWeightedECDSATest:test_disable_mode() (gas: 310650) +KernelWeightedECDSATest:test_eip712() (gas: 15953) +KernelWeightedECDSATest:test_enable_then_mode_1() (gas: 415042) +KernelWeightedECDSATest:test_external_call_batch_execute_fail() (gas: 27875) +KernelWeightedECDSATest:test_external_call_batch_execute_success() (gas: 0) +KernelWeightedECDSATest:test_external_call_default() (gas: 0) +KernelWeightedECDSATest:test_external_call_execute_delegatecall_fail() (gas: 66397) +KernelWeightedECDSATest:test_external_call_execute_delegatecall_option_fail() (gas: 66179) +KernelWeightedECDSATest:test_external_call_execute_delegatecall_success() (gas: 0) +KernelWeightedECDSATest:test_external_call_execute_fail() (gas: 65677) +KernelWeightedECDSATest:test_external_call_execute_success() (gas: 0) +KernelWeightedECDSATest:test_external_call_execution() (gas: 639603) +KernelWeightedECDSATest:test_fail_validate_not_activate() (gas: 304469) +KernelWeightedECDSATest:test_fail_validate_wrongsignature() (gas: 48654) +KernelWeightedECDSATest:test_get_nonce() (gas: 25643) +KernelWeightedECDSATest:test_get_nonce(uint192) (runs: 1024, μ: 20178, ~: 20178) +KernelWeightedECDSATest:test_ignore() (gas: 949) +KernelWeightedECDSATest:test_initialize() (gas: 250808) +KernelWeightedECDSATest:test_initialize_twice() (gas: 33199) +KernelWeightedECDSATest:test_mode_2() (gas: 266572) +KernelWeightedECDSATest:test_revert_when_mode_disabled() (gas: 329650) +KernelWeightedECDSATest:test_set_default_validator() (gas: 550884) +KernelWeightedECDSATest:test_set_execution() (gas: 596300) +KernelWeightedECDSATest:test_should_emit_event_on_receive() (gas: 23500) +KernelWeightedECDSATest:test_should_receive_erc1155() (gas: 700509) +KernelWeightedECDSATest:test_should_receive_erc1155_batch() (gas: 727747) +KernelWeightedECDSATest:test_should_receive_erc721() (gas: 593329) +KernelWeightedECDSATest:test_should_return_address_if_deployed() (gas: 37658) +KernelWeightedECDSATest:test_sudo() (gas: 306510) +KernelWeightedECDSATest:test_sudo_wrongSig() (gas: 158085) +KernelWeightedECDSATest:test_upgrade() (gas: 21643) +KernelWeightedECDSATest:test_validateUserOp_fail_invalid_mode() (gas: 27390) +KernelWeightedECDSATest:test_validateUserOp_fail_not_entryPoint() (gas: 23678) +KernelWeightedECDSATest:test_validate_signature() (gas: 311992) +KillSwitchValidatorTest:test_default_validator_disable() (gas: 180769) +KillSwitchValidatorTest:test_default_validator_enable() (gas: 188261) +KillSwitchValidatorTest:test_disable_mode() (gas: 183271) KillSwitchValidatorTest:test_eip712() (gas: 15820) -KillSwitchValidatorTest:test_enable_then_mode_1() (gas: 348586) -KillSwitchValidatorTest:test_external_call_batch_execute_fail() (gas: 29622) -KillSwitchValidatorTest:test_external_call_batch_execute_success() (gas: 26558) +KillSwitchValidatorTest:test_enable_then_mode_1() (gas: 349996) +KillSwitchValidatorTest:test_external_call_batch_execute_fail() (gas: 29619) +KillSwitchValidatorTest:test_external_call_batch_execute_success() (gas: 26555) KillSwitchValidatorTest:test_external_call_default() (gas: 29426) KillSwitchValidatorTest:test_external_call_execute_delegatecall_fail() (gas: 28469) KillSwitchValidatorTest:test_external_call_execute_delegatecall_option_fail() (gas: 26439) KillSwitchValidatorTest:test_external_call_execute_delegatecall_success() (gas: 25709) KillSwitchValidatorTest:test_external_call_execute_fail() (gas: 27906) KillSwitchValidatorTest:test_external_call_execute_success() (gas: 26075) -KillSwitchValidatorTest:test_external_call_execution() (gas: 510817) -KillSwitchValidatorTest:test_fail_validate_not_activate() (gas: 303601) -KillSwitchValidatorTest:test_fail_validate_wrongsignature() (gas: 34276) -KillSwitchValidatorTest:test_force_unblock() (gas: 391544) +KillSwitchValidatorTest:test_external_call_execution() (gas: 511153) +KillSwitchValidatorTest:test_fail_validate_not_activate() (gas: 303604) +KillSwitchValidatorTest:test_fail_validate_wrongsignature() (gas: 34494) +KillSwitchValidatorTest:test_force_unblock() (gas: 394842) KillSwitchValidatorTest:test_get_nonce() (gas: 25201) KillSwitchValidatorTest:test_get_nonce(uint192) (runs: 1024, μ: 19930, ~: 19930) KillSwitchValidatorTest:test_ignore() (gas: 956) KillSwitchValidatorTest:test_initialize() (gas: 154209) KillSwitchValidatorTest:test_initialize_twice() (gas: 18643) -KillSwitchValidatorTest:test_mode_2() (gas: 312761) -KillSwitchValidatorTest:test_revert_when_mode_disabled() (gas: 199157) -KillSwitchValidatorTest:test_set_default_validator() (gas: 423434) -KillSwitchValidatorTest:test_set_execution() (gas: 468424) +KillSwitchValidatorTest:test_mode_2() (gas: 313713) +KillSwitchValidatorTest:test_revert_when_mode_disabled() (gas: 200273) +KillSwitchValidatorTest:test_set_default_validator() (gas: 423773) +KillSwitchValidatorTest:test_set_execution() (gas: 468769) KillSwitchValidatorTest:test_should_emit_event_on_receive() (gas: 23330) KillSwitchValidatorTest:test_should_fail_with_not_implemented_isValidSignature() (gas: 9236) KillSwitchValidatorTest:test_should_fail_with_not_implemented_isValidSignature(bytes32,bytes) (runs: 1024, μ: 9601, ~: 9556) KillSwitchValidatorTest:test_should_fail_with_not_implemented_validCaller() (gas: 10157) KillSwitchValidatorTest:test_should_fail_with_not_implemented_validCaller(address,bytes) (runs: 1024, μ: 9749, ~: 9703) -KillSwitchValidatorTest:test_should_receive_erc1155() (gas: 701233) -KillSwitchValidatorTest:test_should_receive_erc1155_batch() (gas: 728499) -KillSwitchValidatorTest:test_should_receive_erc721() (gas: 597790) +KillSwitchValidatorTest:test_should_receive_erc1155() (gas: 700387) +KillSwitchValidatorTest:test_should_receive_erc1155_batch() (gas: 727584) +KillSwitchValidatorTest:test_should_receive_erc721() (gas: 593185) KillSwitchValidatorTest:test_should_return_address_if_deployed() (gas: 22086) -KillSwitchValidatorTest:test_sudo() (gas: 179549) -KillSwitchValidatorTest:test_sudo_wrongSig() (gas: 115830) +KillSwitchValidatorTest:test_sudo() (gas: 179882) +KillSwitchValidatorTest:test_sudo_wrongSig() (gas: 116442) KillSwitchValidatorTest:test_upgrade() (gas: 21314) -KillSwitchValidatorTest:test_validateUserOp_fail_invalid_mode() (gas: 26638) -KillSwitchValidatorTest:test_validateUserOp_fail_not_entryPoint() (gas: 30708) -KillSwitchValidatorTest:test_validate_signature() (gas: 194579) -SessionKeyValidatorTest:test_default_validator_disable() (gas: 180311) -SessionKeyValidatorTest:test_default_validator_enable() (gas: 187800) -SessionKeyValidatorTest:test_disable_mode() (gas: 182795) +KillSwitchValidatorTest:test_validateUserOp_fail_invalid_mode() (gas: 27022) +KillSwitchValidatorTest:test_validateUserOp_fail_not_entryPoint() (gas: 23391) +KillSwitchValidatorTest:test_validate_signature() (gas: 195249) +SessionKeyValidatorTest:test_default_validator_disable() (gas: 180670) +SessionKeyValidatorTest:test_default_validator_enable() (gas: 188140) +SessionKeyValidatorTest:test_disable_mode() (gas: 183150) SessionKeyValidatorTest:test_eip712() (gas: 15684) -SessionKeyValidatorTest:test_enable_then_mode_1() (gas: 286128) -SessionKeyValidatorTest:test_external_call_batch_execute_fail() (gas: 29534) -SessionKeyValidatorTest:test_external_call_batch_execute_success() (gas: 26536) -SessionKeyValidatorTest:test_external_call_default() (gas: 29338) +SessionKeyValidatorTest:test_enable_then_mode_1() (gas: 287446) +SessionKeyValidatorTest:test_external_call_batch_execute_fail() (gas: 29553) +SessionKeyValidatorTest:test_external_call_batch_execute_success() (gas: 26533) +SessionKeyValidatorTest:test_external_call_default() (gas: 29360) SessionKeyValidatorTest:test_external_call_execute_delegatecall_fail() (gas: 28381) -SessionKeyValidatorTest:test_external_call_execute_delegatecall_option_fail() (gas: 26351) +SessionKeyValidatorTest:test_external_call_execute_delegatecall_option_fail() (gas: 26373) SessionKeyValidatorTest:test_external_call_execute_delegatecall_success() (gas: 25621) SessionKeyValidatorTest:test_external_call_execute_fail() (gas: 27862) -SessionKeyValidatorTest:test_external_call_execute_success() (gas: 26009) -SessionKeyValidatorTest:test_external_call_execution() (gas: 510718) -SessionKeyValidatorTest:test_fail_validate_not_activate() (gas: 303513) -SessionKeyValidatorTest:test_fail_validate_wrongsignature() (gas: 34165) +SessionKeyValidatorTest:test_external_call_execute_success() (gas: 26031) +SessionKeyValidatorTest:test_external_call_execution() (gas: 511076) +SessionKeyValidatorTest:test_fail_validate_not_activate() (gas: 303538) +SessionKeyValidatorTest:test_fail_validate_wrongsignature() (gas: 34395) SessionKeyValidatorTest:test_get_nonce() (gas: 25179) SessionKeyValidatorTest:test_get_nonce(uint192) (runs: 1024, μ: 19842, ~: 19842) -SessionKeyValidatorTest:test_ignore() (gas: 890) +SessionKeyValidatorTest:test_ignore() (gas: 912) SessionKeyValidatorTest:test_initialize() (gas: 154121) SessionKeyValidatorTest:test_initialize_twice() (gas: 18555) -SessionKeyValidatorTest:test_mode_2() (gas: 250298) -SessionKeyValidatorTest:test_revert_when_mode_disabled() (gas: 199003) -SessionKeyValidatorTest:test_scenario_batch((uint8,uint8,uint8,uint8,uint48,uint48,uint48,uint48,bool,bool,bool,bool),(uint8)) (runs: 1024, μ: 43997409, ~: 32002162) -SessionKeyValidatorTest:test_scenario_non_batch((uint8,uint8,uint8,uint8,uint48,uint48,uint48,uint48,bool,bool,bool,bool)) (runs: 1024, μ: 17511495, ~: 17372686) -SessionKeyValidatorTest:test_set_default_validator() (gas: 423335) -SessionKeyValidatorTest:test_set_execution() (gas: 468303) -SessionKeyValidatorTest:test_should_emit_event_on_receive() (gas: 23242) -SessionKeyValidatorTest:test_should_receive_erc1155() (gas: 701145) -SessionKeyValidatorTest:test_should_receive_erc1155_batch() (gas: 728411) -SessionKeyValidatorTest:test_should_receive_erc721() (gas: 597724) -SessionKeyValidatorTest:test_should_return_address_if_deployed() (gas: 22003) -SessionKeyValidatorTest:test_sudo() (gas: 179450) -SessionKeyValidatorTest:test_sudo_wrongSig() (gas: 115731) +SessionKeyValidatorTest:test_mode_2() (gas: 251156) +SessionKeyValidatorTest:test_revert_when_mode_disabled() (gas: 200037) +SessionKeyValidatorTest:test_scenario_batch((uint8,uint8,uint8,uint8,uint48,uint48,uint48,uint48,bool,bool,bool,bool,bool),(uint8)) (runs: 1024, μ: 55953243, ~: 51234062) +SessionKeyValidatorTest:test_scenario_non_batch((uint8,uint8,uint8,uint8,uint48,uint48,uint48,uint48,bool,bool,bool,bool,bool)) (runs: 1024, μ: 50850417, ~: 50878988) +SessionKeyValidatorTest:test_set_default_validator() (gas: 423674) +SessionKeyValidatorTest:test_set_execution() (gas: 468648) +SessionKeyValidatorTest:test_should_emit_event_on_receive() (gas: 23264) +SessionKeyValidatorTest:test_should_receive_erc1155() (gas: 700299) +SessionKeyValidatorTest:test_should_receive_erc1155_batch() (gas: 727496) +SessionKeyValidatorTest:test_should_receive_erc721() (gas: 593141) +SessionKeyValidatorTest:test_should_return_address_if_deployed() (gas: 22020) +SessionKeyValidatorTest:test_sudo() (gas: 179783) +SessionKeyValidatorTest:test_sudo_wrongSig() (gas: 116365) SessionKeyValidatorTest:test_upgrade() (gas: 21292) -SessionKeyValidatorTest:test_validateUserOp_fail_invalid_mode() (gas: 26574) -SessionKeyValidatorTest:test_validateUserOp_fail_not_entryPoint() (gas: 30634) -SessionKeyValidatorTest:test_validate_signature() (gas: 194502) -TestCallee:test_ignore() (gas: 206) +SessionKeyValidatorTest:test_validateUserOp_fail_invalid_mode() (gas: 26958) +SessionKeyValidatorTest:test_validateUserOp_fail_not_entryPoint() (gas: 23364) +SessionKeyValidatorTest:test_validate_signature() (gas: 195172) +TestCallee:test_ignore() (gas: 249) TestCounter:test_ignore() (gas: 164) -TestERC1155:test_ignore() (gas: 328) +TestERC1155:test_ignore() (gas: 313) TestERC20:test_ignore() (gas: 389) TestERC721:test_ignore() (gas: 399) TestExecutor:test_ignore() (gas: 130) TestPaymaster:test_ignore() (gas: 174) -TestValidator:test_ignore() (gas: 351) \ No newline at end of file +TestValidator:test_ignore() (gas: 351) +WebAuthnFclValidatorTest:test_default_validator_disable() (gas: 599624) +WebAuthnFclValidatorTest:test_default_validator_enable() (gas: 610462) +WebAuthnFclValidatorTest:test_disable_mode() (gas: 606941) +WebAuthnFclValidatorTest:test_dontRevertForDummySig() (gas: 32708) +WebAuthnFclValidatorTest:test_eip712() (gas: 15953) +WebAuthnFclValidatorTest:test_enable_then_mode_1() (gas: 725427) +WebAuthnFclValidatorTest:test_external_call_batch_execute_fail() (gas: 25082) +WebAuthnFclValidatorTest:test_external_call_batch_execute_success() (gas: 0) +WebAuthnFclValidatorTest:test_external_call_default() (gas: 0) +WebAuthnFclValidatorTest:test_external_call_execute_delegatecall_fail() (gas: 0) +WebAuthnFclValidatorTest:test_external_call_execute_delegatecall_option_fail() (gas: 21982) +WebAuthnFclValidatorTest:test_external_call_execute_delegatecall_success() (gas: 0) +WebAuthnFclValidatorTest:test_external_call_execute_fail() (gas: 21228) +WebAuthnFclValidatorTest:test_external_call_execute_success() (gas: 0) +WebAuthnFclValidatorTest:test_external_call_execution() (gas: 0) +WebAuthnFclValidatorTest:test_fail_validate_InvalidWebAuthnData() (gas: 55738) +WebAuthnFclValidatorTest:test_fail_validate_not_activate() (gas: 304424) +WebAuthnFclValidatorTest:test_fail_validate_wrongsignature() (gas: 450136) +WebAuthnFclValidatorTest:test_get_nonce() (gas: 25621) +WebAuthnFclValidatorTest:test_get_nonce(uint192) (runs: 1024, μ: 20156, ~: 20156) +WebAuthnFclValidatorTest:test_initialize() (gas: 178514) +WebAuthnFclValidatorTest:test_initialize_twice() (gas: 20827) +WebAuthnFclValidatorTest:test_mode_2() (gas: 698102) +WebAuthnFclValidatorTest:test_revert_when_mode_disabled() (gas: 623939) +WebAuthnFclValidatorTest:test_set_default_validator() (gas: 844997) +WebAuthnFclValidatorTest:test_set_execution() (gas: 897931) +WebAuthnFclValidatorTest:test_should_emit_event_on_receive() (gas: 23456) +WebAuthnFclValidatorTest:test_should_receive_erc1155() (gas: 700487) +WebAuthnFclValidatorTest:test_should_receive_erc1155_batch() (gas: 727747) +WebAuthnFclValidatorTest:test_should_receive_erc721() (gas: 593263) +WebAuthnFclValidatorTest:test_should_return_address_if_deployed() (gas: 24521) +WebAuthnFclValidatorTest:test_sudo() (gas: 602311) +WebAuthnFclValidatorTest:test_sudo_wrongSig() (gas: 531738) +WebAuthnFclValidatorTest:test_upgrade() (gas: 21643) +WebAuthnFclValidatorTest:test_validateUserOp_fail_invalid_mode() (gas: 27368) +WebAuthnFclValidatorTest:test_validateUserOp_fail_not_entryPoint() (gas: 23656) +WebAuthnFclValidatorTest:test_validate_signature() (gas: 466394) +WebAuthnFclValidatorTest:test_webAuthnSignatureGeneration(bytes32,uint256) (runs: 1024, μ: 925827, ~: 849019) +WebAuthnFclValidatorTest:test_webAuthnSignatureGeneration_solo() (gas: 837725) \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 09880b1d..bf068fdb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,12 @@ name: test -on: workflow_dispatch +on: + workflow_dispatch: + pull_request: + push: + branches: + - "main" + - "dev" env: FOUNDRY_PROFILE: ci @@ -32,3 +38,10 @@ jobs: run: | forge test -vvv id: test + + - name: Run snapshot + run: NO_COLOR=1 forge snapshot --via-ir >> $GITHUB_STEP_SUMMARY + id: snapshot + + - name: Run coverage + run: NO_COLOR=1 forge coverage >> $GITHUB_STEP_SUMMARY diff --git a/.gitmodules b/.gitmodules index 467f62e8..14110192 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +1,12 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "lib/solady"] - path = lib/solady - url = https://github.com/vectorized/solady [submodule "lib/I4337"] path = lib/I4337 url = https://github.com/leekt/I4337 -[submodule "lib/p256-verifier"] - path = lib/p256-verifier - url = https://github.com/daimo-eth/p256-verifier +[submodule "lib/FreshCryptoLib"] + path = lib/FreshCryptoLib + url = https://github.com/rdubois-crypto/FreshCryptoLib +[submodule "lib/solady"] + path = lib/solady + url = https://github.com/vectorized/solady \ No newline at end of file diff --git a/README.md b/README.md index f701be30..db45b3d6 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Kernel is supported by all major AA SDKs, including: ## Resources -- [Developing plugins](https://docs.zerodev.app/extend-wallets/overview) +- [Learn more about plugins](https://docs.zerodev.app/sdk/plugins/intro) - [Read the source code](https://github.com/zerodevapp/kernel) ## Build diff --git a/audits/kalos_recovery_v2.pdf b/audits/kalos_recovery_v2.pdf new file mode 100644 index 00000000..d00c0642 Binary files /dev/null and b/audits/kalos_recovery_v2.pdf differ diff --git a/remappings.txt b/remappings.txt index ad991f0e..db0b91c6 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,6 +1,5 @@ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ solady/=lib/solady/src/ -p256-verifier/=lib/p256-verifier/src/ -FreshCryptoLib/=lib/FreshCryptoLib/solidity/src/ I4337/=lib/I4337/src/ +FreshCryptoLib/=lib/FreshCryptoLib/solidity/src/ \ No newline at end of file diff --git a/script/DeployWebAuthnFclValidator.sol b/script/DeployWebAuthnFclValidator.sol new file mode 100644 index 00000000..94437add --- /dev/null +++ b/script/DeployWebAuthnFclValidator.sol @@ -0,0 +1,22 @@ +pragma solidity ^0.8.0; + +import "src/factory/KernelFactory.sol"; +import "src/utils/P256VerifierWrapper.sol"; +import "src/validator/webauthn//WebAuthnFclValidator.sol"; +import "forge-std/Script.sol"; +import "forge-std/console.sol"; + +contract DeployWebAuthnFclValidator is Script { + function run() public { + uint256 key = vm.envUint("DEPLOYER_PRIVATE_KEY"); + vm.startBroadcast(key); + + P256VerifierWrapper p256VerifierWrapper = new P256VerifierWrapper{salt: 0}(); + console.log("p256 wrapper address: %s", address(p256VerifierWrapper)); + + WebAuthnFclValidator validator = new WebAuthnFclValidator{salt: 0}(address(p256VerifierWrapper)); + console.log("validator address: %s", address(validator)); + + vm.stopBroadcast(); + } +} diff --git a/src/Kernel.sol b/src/Kernel.sol index e3da874f..f2e7c2ba 100644 --- a/src/Kernel.sol +++ b/src/Kernel.sol @@ -28,6 +28,8 @@ import {ValidationData, ValidAfter, ValidUntil, parseValidationData, packValidat contract Kernel is EIP712, Compatibility, KernelStorage { /// @dev Selector of the `DisabledMode()` error, to be used in assembly, 'bytes4(keccak256(bytes("DisabledMode()")))', same as DisabledMode.selector() uint256 private constant _DISABLED_MODE_SELECTOR = 0xfc2f51c5; + bytes32 internal constant EIP712_DOMAIN_TYPEHASH = + 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; /// @dev Current kernel name and version string public constant name = KERNEL_NAME; @@ -288,8 +290,7 @@ contract Kernel is EIP712, Compatibility, KernelStorage { address proxyAddress = address(this); // Construct the domain separator with name, version, chainId, and proxy address. - bytes32 typeHash = - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + bytes32 typeHash = EIP712_DOMAIN_TYPEHASH; return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, proxyAddress)); } diff --git a/src/utils/KernelTestBase.sol b/src/utils/KernelTestBase.sol index 04c37aca..6c48b09f 100644 --- a/src/utils/KernelTestBase.sol +++ b/src/utils/KernelTestBase.sol @@ -15,10 +15,11 @@ import {IKernelValidator} from "../interfaces/IKernelValidator.sol"; import {Call, ExecutionDetail} from "../common/Structs.sol"; import {ValidationData, ValidUntil, ValidAfter} from "../common/Types.sol"; import {KERNEL_VERSION, KERNEL_NAME} from "../common/Constants.sol"; +import {ECDSA} from "solady/utils/ECDSA.sol"; import {ERC4337Utils} from "./ERC4337Utils.sol"; import {Test} from "forge-std/Test.sol"; -import {console} from "forge-std/Console.sol"; +import {console} from "forge-std/console.sol"; import {TestValidator} from "../mock/TestValidator.sol"; import {TestExecutor} from "../mock/TestExecutor.sol"; import {TestERC721} from "../mock/TestERC721.sol"; @@ -196,7 +197,8 @@ abstract contract KernelTestBase is Test { function test_validate_signature() external virtual { Kernel kernel2 = Kernel(payable(factory.createAccount(address(kernelImpl), getInitializeData(), 3))); - bytes32 hash = keccak256(abi.encodePacked("hello world")); + string memory message = "hello world"; + bytes32 hash = ECDSA.toEthSignedMessageHash(bytes(message)); bytes32 digest = keccak256( abi.encodePacked( "\x19\x01", ERC4337Utils._buildDomainSeparator(KERNEL_NAME, KERNEL_VERSION, address(kernel)), hash diff --git a/src/utils/P256VerifierWrapper.sol b/src/utils/P256VerifierWrapper.sol new file mode 100644 index 00000000..bc6340ac --- /dev/null +++ b/src/utils/P256VerifierWrapper.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {FCL_ecdsa} from "FreshCryptoLib/FCL_ecdsa.sol"; + +/// @title P256VerifierWrapper +/// @author rdubois-crypto +/// @author KONFeature +/// @notice Wrapper arround the P256Verifier contract of @rdubois-crypto, using it to accept EIP-7212 compliant verification (p256 pre-compiled curve) +/// @dev This lib is only a wrapper around the P256Verifier contract. +/// It will call the verifySignature function of the P256Verifier contract. +/// Once the RIP-7212 will be deployed and effective, this contract will be useless. +/// Tracker on polygon: PR: https://github.com/maticnetwork/bor/pull/1069 +/// Now waiting on the Napoli hardfork to be deployed +contract P256VerifierWrapper { + /** + * Precompiles don't use a function signature. The first byte of callldata + * is the first byte of an input argument. In this case: + * + * input[ 0: 32] = signed data hash + * input[ 32: 64] = signature r + * input[ 64: 96] = signature s + * input[ 96:128] = public key x + * input[128:160] = public key y + * + * result[ 0: 32] = 0x00..00 (invalid) or 0x00..01 (valid) + * + * For details, see https://eips.ethereum.org/EIPS/eip-7212 + */ + fallback(bytes calldata input) external returns (bytes memory) { + if (input.length != 160) { + return abi.encodePacked(uint256(0)); + } + + bytes32 hash = bytes32(input[0:32]); + uint256 r = uint256(bytes32(input[32:64])); + uint256 s = uint256(bytes32(input[64:96])); + uint256 x = uint256(bytes32(input[96:128])); + uint256 y = uint256(bytes32(input[128:160])); + + uint256 ret = FCL_ecdsa.ecdsa_verify(hash, r, s, x, y) ? 1 : 0; + + return abi.encodePacked(ret); + } +} diff --git a/src/validator/WeightedECDSAValidator.sol b/src/validator/WeightedECDSAValidator.sol index f0a8f993..d495b945 100644 --- a/src/validator/WeightedECDSAValidator.sol +++ b/src/validator/WeightedECDSAValidator.sol @@ -51,34 +51,45 @@ contract WeightedECDSAValidator is EIP712, IKernelValidator { event GuardianRemoved(address indexed guardian, address indexed kernel); function _domainNameAndVersion() internal pure override returns (string memory, string memory) { - return ("WeightedECDSAValidator", "0.0.1"); + return ("WeightedECDSAValidator", "0.0.3"); } - function enable(bytes calldata _data) external payable override { - (address[] memory _guardians, uint24[] memory _weights, uint24 _threshold, uint48 _delay) = - abi.decode(_data, (address[], uint24[], uint24, uint48)); + function _addGuardians(address[] memory _guardians, uint24[] memory _weights, address _kernel) internal { + uint24 totalWeight = weightedStorage[_kernel].totalWeight; require(_guardians.length == _weights.length, "Length mismatch"); - require(weightedStorage[msg.sender].totalWeight == 0, "Already enabled"); - weightedStorage[msg.sender].firstGuardian = msg.sender; + uint160 prevGuardian = uint160(weightedStorage[_kernel].firstGuardian); for (uint256 i = 0; i < _guardians.length; i++) { - require(_guardians[i] != msg.sender, "Guardian cannot be self"); + require(_guardians[i] != _kernel, "Guardian cannot be self"); require(_guardians[i] != address(0), "Guardian cannot be 0"); require(_weights[i] != 0, "Weight cannot be 0"); - require(guardian[_guardians[i]][msg.sender].weight == 0, "Guardian already enabled"); - guardian[_guardians[i]][msg.sender] = - GuardianStorage({weight: _weights[i], nextGuardian: weightedStorage[msg.sender].firstGuardian}); - weightedStorage[msg.sender].firstGuardian = _guardians[i]; - weightedStorage[msg.sender].totalWeight += _weights[i]; - emit GuardianAdded(_guardians[i], msg.sender, _weights[i]); + require(guardian[_guardians[i]][_kernel].weight == 0, "Guardian already enabled"); + require(uint160(_guardians[i]) < prevGuardian, "Guardians not sorted"); + guardian[_guardians[i]][_kernel] = + GuardianStorage({weight: _weights[i], nextGuardian: weightedStorage[_kernel].firstGuardian}); + weightedStorage[_kernel].firstGuardian = _guardians[i]; + totalWeight += _weights[i]; + prevGuardian = uint160(_guardians[i]); + emit GuardianAdded(_guardians[i], _kernel, _weights[i]); } + weightedStorage[_kernel].totalWeight = totalWeight; + } + + function enable(bytes calldata _data) external payable override { + (address[] memory _guardians, uint24[] memory _weights, uint24 _threshold, uint48 _delay) = + abi.decode(_data, (address[], uint24[], uint24, uint48)); + require(_guardians.length == _weights.length, "Length mismatch"); + require(weightedStorage[msg.sender].totalWeight == 0, "Already enabled"); + weightedStorage[msg.sender].firstGuardian = address(uint160(type(uint160).max)); + _addGuardians(_guardians, _weights, msg.sender); weightedStorage[msg.sender].delay = _delay; weightedStorage[msg.sender].threshold = _threshold; + require(_threshold <= weightedStorage[msg.sender].totalWeight, "Threshold too high"); } function disable(bytes calldata) external payable override { require(weightedStorage[msg.sender].totalWeight != 0, "Not enabled"); address currentGuardian = weightedStorage[msg.sender].firstGuardian; - while (currentGuardian != msg.sender) { + while (currentGuardian != address(uint160(type(uint160).max))) { address nextGuardian = guardian[currentGuardian][msg.sender].nextGuardian; emit GuardianRemoved(currentGuardian, msg.sender); delete guardian[currentGuardian][msg.sender]; @@ -101,18 +112,8 @@ contract WeightedECDSAValidator is EIP712, IKernelValidator { } delete weightedStorage[msg.sender]; require(_guardians.length == _weights.length, "Length mismatch"); - weightedStorage[msg.sender].firstGuardian = msg.sender; - for (uint256 i = 0; i < _guardians.length; i++) { - require(_guardians[i] != msg.sender, "Guardian cannot be self"); - require(_guardians[i] != address(0), "Guardian cannot be 0"); - require(_weights[i] != 0, "Weight cannot be 0"); - require(guardian[_guardians[i]][msg.sender].weight == 0, "Guardian already enabled"); - guardian[_guardians[i]][msg.sender] = - GuardianStorage({weight: _weights[i], nextGuardian: weightedStorage[msg.sender].firstGuardian}); - weightedStorage[msg.sender].firstGuardian = _guardians[i]; - weightedStorage[msg.sender].totalWeight += _weights[i]; - emit GuardianAdded(_guardians[i], msg.sender, _weights[i]); - } + weightedStorage[msg.sender].firstGuardian = _guardians[0]; + _addGuardians(_guardians, _weights, msg.sender); weightedStorage[msg.sender].delay = _delay; weightedStorage[msg.sender].threshold = _threshold; } @@ -165,7 +166,7 @@ contract WeightedECDSAValidator is EIP712, IKernelValidator { proposal.status = ProposalStatus.Rejected; } - function validateUserOp(UserOperation calldata userOp, bytes32, uint256) + function validateUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256) external payable returns (ValidationData validationData) @@ -186,10 +187,10 @@ contract WeightedECDSAValidator is EIP712, IKernelValidator { bytes calldata sig = userOp.signature; // parse sig with 65 bytes uint256 sigCount = sig.length / 65; + require(sigCount > 0, "No sig"); address signer; VoteStorage storage vote; - for (uint256 i = 0; i < sigCount && !passed; i++) { - // last sig is for userOpHash verification + for (uint256 i = 0; i < sigCount - 1 && !passed; i++) { signer = ECDSA.recover( _hashTypedData( keccak256(abi.encode(keccak256("Approve(bytes32 callDataAndNonceHash)"), callDataAndNonceHash)) @@ -206,18 +207,33 @@ contract WeightedECDSAValidator is EIP712, IKernelValidator { passed = true; } } - if (passed) { - validationData = packValidationData(ValidAfter.wrap(0), ValidUntil.wrap(0)); + // userOpHash verification for the last sig + signer = ECDSA.recover(ECDSA.toEthSignedMessageHash(userOpHash), sig[sig.length - 65:]); + vote = voteStatus[callDataAndNonceHash][signer][msg.sender]; + if (vote.status == VoteStatus.NA) { + vote.status = VoteStatus.Approved; + totalWeight += guardian[signer][msg.sender].weight; + if (totalWeight >= threshold) { + passed = true; + } + } + if (passed && guardian[signer][msg.sender].weight != 0) { proposal.status = ProposalStatus.Executed; - } else { - validationData = SIG_VALIDATION_FAILED; + return packValidationData(ValidAfter.wrap(0), ValidUntil.wrap(0)); } } else if (proposal.status == ProposalStatus.Approved || passed) { - validationData = packValidationData(proposal.validAfter, ValidUntil.wrap(0)); - proposal.status = ProposalStatus.Executed; - } else { - return SIG_VALIDATION_FAILED; + if (userOp.paymasterAndData.length == 0 || address(bytes20(userOp.paymasterAndData[0:20])) == address(0)) { + address signer = ECDSA.recover(ECDSA.toEthSignedMessageHash(userOpHash), userOp.signature); + if (guardian[signer][msg.sender].weight != 0) { + proposal.status = ProposalStatus.Executed; + return packValidationData(proposal.validAfter, ValidUntil.wrap(0)); + } + } else { + proposal.status = ProposalStatus.Executed; + return packValidationData(proposal.validAfter, ValidUntil.wrap(0)); + } } + return SIG_VALIDATION_FAILED; } function getApproval(address kernel, bytes32 hash) public view returns (uint256 approvals, bool passed) { @@ -238,7 +254,29 @@ contract WeightedECDSAValidator is EIP712, IKernelValidator { return false; } - function validateSignature(bytes32, bytes calldata) external pure returns (ValidationData) { + function validateSignature(bytes32 hash, bytes calldata signature) external view returns (ValidationData) { + WeightedECDSAValidatorStorage storage strg = weightedStorage[msg.sender]; + if (strg.threshold == 0) { + return SIG_VALIDATION_FAILED; + } + + uint256 sigCount = signature.length / 65; + if (sigCount == 0) { + return SIG_VALIDATION_FAILED; + } + uint256 totalWeight = 0; + address prevSigner = address(uint160(type(uint160).max)); + for (uint256 i = 0; i < sigCount; i++) { + address signer = ECDSA.recover(hash, signature[i * 65:(i + 1) * 65]); + totalWeight += guardian[signer][msg.sender].weight; + if (totalWeight >= strg.threshold) { + return packValidationData(ValidAfter.wrap(0), ValidUntil.wrap(0)); + } + if (signer >= prevSigner) { + return SIG_VALIDATION_FAILED; + } + prevSigner = signer; + } return SIG_VALIDATION_FAILED; } } diff --git a/src/validator/webauthn/WebAuthnFclValidator.sol b/src/validator/webauthn/WebAuthnFclValidator.sol new file mode 100644 index 00000000..fbbc41ed --- /dev/null +++ b/src/validator/webauthn/WebAuthnFclValidator.sol @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {UserOperation} from "I4337/interfaces/UserOperation.sol"; +import {ECDSA} from "solady/utils/ECDSA.sol"; +import {IKernelValidator} from "../../interfaces/IKernelValidator.sol"; +import {ValidationData} from "../../common/Types.sol"; +import {SIG_VALIDATION_FAILED} from "../../common/Constants.sol"; +import {WebAuthnFclVerifier} from "./WebAuthnFclVerifier.sol"; + +/// @dev Storage layout for a kernel in the WebAuthnValidator contract. +struct WebAuthnFclValidatorStorage { + /// @dev The `x` coord of the secp256r1 public key used to sign the user operation. + uint256 x; + /// @dev The `y` coord of the secp256r1 public key used to sign the user operation. + uint256 y; +} + +/// @author @KONFeature +/// @title WebAuthnFclValidator +/// @notice Kernel validator used to validated user operations via WebAuthn signature (using P256 under the hood) +/// @notice Using the awesome FreshCryptoLib: https://github.com/rdubois-crypto/FreshCryptoLib/ +/// @notice Inspired by the cometh Gnosis Safe signer: https://github.com/cometh-game/p256-signer +contract WebAuthnFclValidator is IKernelValidator { + /// @dev Event emitted when the public key signing the WebAuthN user operation is changed for a given `kernel`. + event WebAuthnPublicKeyChanged(address indexed kernel, uint256 x, uint256 y); + + /// @dev Mapping of kernel address to each webAuthn specific storage + mapping(address kernel => WebAuthnFclValidatorStorage webAuthnStorage) private webAuthnValidatorStorage; + + /// @dev The address of the on-chain p256 verifier contract (will be used if the user want that instead of the pre-compiled one, that way this validator can work on every chain out of the box while rip7212 is slowly being implemented everywhere) + address private immutable P256_VERIFIER; + + /// @dev Simple constructor, setting the P256 verifier address + constructor(address _p256Verifier) { + P256_VERIFIER = _p256Verifier; + } + + /// @dev Disable this validator for a given `kernel` (msg.sender) + function disable(bytes calldata) external payable override { + delete webAuthnValidatorStorage[msg.sender]; + } + + /// @dev Enable this validator for a given `kernel` (msg.sender) + function enable(bytes calldata _data) external payable override { + // Extract the x & y coordinates of the public key from the `_data` bytes + (uint256 x, uint256 y) = abi.decode(_data, (uint256, uint256)); + // Update the pub key data + WebAuthnFclValidatorStorage storage kernelValidatorStorage = webAuthnValidatorStorage[msg.sender]; + kernelValidatorStorage.x = x; + kernelValidatorStorage.y = y; + // Emit the update event + emit WebAuthnPublicKeyChanged(msg.sender, x, y); + } + + /// @dev Validate a `_userOp` using a WebAuthn Signature for the kernel account who is the `_userOp` sender + function validateUserOp(UserOperation calldata _userOp, bytes32 _userOpHash, uint256) + external + payable + override + returns (ValidationData validationData) + { + WebAuthnFclValidatorStorage memory kernelValidatorStorage = webAuthnValidatorStorage[_userOp.sender]; + + // Perform a check against the direct userOpHash, if ok consider the user op as validated + if (_checkSignature(kernelValidatorStorage, _userOpHash, _userOp.signature)) { + return ValidationData.wrap(0); + } + + return SIG_VALIDATION_FAILED; + } + + /// @dev Validate a `_signature` of the `_hash` ofor the given `kernel` (msg.sender) + function validateSignature(bytes32 _hash, bytes calldata _signature) + public + view + override + returns (ValidationData) + { + WebAuthnFclValidatorStorage memory kernelValidatorStorage = webAuthnValidatorStorage[msg.sender]; + + // Check the validity againt the hash directly + if (_checkSignature(kernelValidatorStorage, _hash, _signature)) { + return ValidationData.wrap(0); + } + + // Otherwise, all good + return SIG_VALIDATION_FAILED; + } + + /// @notice Validates the given `_signature` againt the `_hash` for the given `kernel` (msg.sender) + /// @param _kernelValidatorStorage The kernel storage replication (helping us to fetch the X & Y points of the public key) + /// @param _hash The hash signed + /// @param _signature The signature + function _checkSignature( + WebAuthnFclValidatorStorage memory _kernelValidatorStorage, + bytes32 _hash, + bytes calldata _signature + ) private view returns (bool isValid) { + // Extract the first byte of the signature to check + return WebAuthnFclVerifier._verifyWebAuthNSignature( + P256_VERIFIER, _hash, _signature, _kernelValidatorStorage.x, _kernelValidatorStorage.y + ); + } + + /// @dev Check if the caller is a valid signer, this don't apply to the WebAuthN validator, since it's using a public key + function validCaller(address, bytes calldata) external pure override returns (bool) { + revert NotImplemented(); + } + + /* -------------------------------------------------------------------------- */ + /* Public view methods */ + /* -------------------------------------------------------------------------- */ + + /// @dev Get the owner of a given `kernel` + function getPublicKey(address _kernel) public view returns (uint256 x, uint256 y) { + // Compute the storage slot + WebAuthnFclValidatorStorage storage kernelValidatorStorage = webAuthnValidatorStorage[_kernel]; + + // Access it for x and y + x = kernelValidatorStorage.x; + y = kernelValidatorStorage.y; + } + + /// @dev Check if the pre-compiled p256 verifier is available on this chain + function isPreCompiledP256Available() public view returns (bool) { + // Test signature data, from https://gist.github.com/ulerdogan/8f1714895e23a54147fc529ea30517eb + bytes memory testSignatureData = + hex"4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e"; + + // Perform the static call + (bool success, bytes memory data) = WebAuthnFclVerifier.PRECOMPILED_P256_VERIFIER.staticcall(testSignatureData); + if (!success || data.length == 0) { + return false; + } + + // Decode the result + uint256 result = abi.decode(data, (uint256)); + + // Check it's 1 (valid signature) + return result == uint256(1); + } +} diff --git a/src/validator/webauthn/WebAuthnFclVerifier.sol b/src/validator/webauthn/WebAuthnFclVerifier.sol new file mode 100644 index 00000000..02d9aaf1 --- /dev/null +++ b/src/validator/webauthn/WebAuthnFclVerifier.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Base64} from "solady/utils/Base64.sol"; + +/// @title WebAuthnFclVerifier +/// @author rdubois-crypto +/// @author obatirou +/// @author KONFeature +/// @notice A library used to format webauthn stuff into verifiable p256 messages msg +/// From https://github.com/cometh-hq/p256-signer/blob/09319213276da69aad6d96fa75cd339726f78bb9/contracts/P256Signer.sol +/// And https://github.com/rdubois-crypto/FreshCryptoLib/blob/master/solidity/src/FCL_Webauthn.sol +library WebAuthnFclVerifier { + /// @dev Error thrown when the webauthn data is invalid + error InvalidWebAuthNData(); + + /// @dev 'bytes4(keccak256("InvalidWebAuthNData()"))' + uint256 private constant _INVALID_WEBAUTHN_DATA_SELECTOR = 0x81177746; + + /// @dev the data flag mask we will use to verify the signature + /// @dev Always 0x01 for user presence flag -> https://www.w3.org/TR/webauthn-2/#concept-user-present + bytes1 private constant AUTHENTICATOR_DATA_FLAG_MASK = 0x01; + + /// @dev The address of the pre-compiled p256 verifier contract (following RIP-7212) + address internal constant PRECOMPILED_P256_VERIFIER = address(0x100); + + /// @dev layout of a signature (used to extract the reauired payload from the initial calldata) + struct FclSignatureLayout { + bool useOnChainP256Verifier; + bytes authenticatorData; + bytes clientData; + uint256 challengeOffset; + uint256[2] rs; + } + + /// @dev Format the webauthn challenge into a p256 message + /// @dev return the raw message that has been signed by the user on the p256 curve + /// @dev Logic from https://github.com/rdubois-crypto/FreshCryptoLib/blob/master/solidity/src/FCL_Webauthn.sol + /// @param _hash The hash that has been signed via WebAuthN + /// @param _signature The signature that has been provided with the userOp + /// @return p256Message The message that has been signed on the p256 curve + function _formatWebAuthNChallenge(bytes32 _hash, FclSignatureLayout calldata _signature) + internal + pure + returns (bytes32 p256Message) + { + // Extract a few calldata pointer we will use to format / verify our msg + bytes calldata authenticatorData = _signature.authenticatorData; + bytes calldata clientData = _signature.clientData; + uint256 challengeOffset = _signature.challengeOffset; + + // If the challenge offset is uint256 max, it's mean that we are in the case of a dummy sig, so we can skip the check and just return the hash + if (challengeOffset == type(uint256).max) { + return _hash; + } + + // Otherwise, perform the complete format and checks of the data + { + // Let the caller check if User Presence (0x01) or User Verification (0x04) are set + if ((authenticatorData[32] & AUTHENTICATOR_DATA_FLAG_MASK) != AUTHENTICATOR_DATA_FLAG_MASK) { + revert InvalidWebAuthNData(); + } + // Verify that clientData commits to the expected client challenge + // Use the Base64Url encoding which omits padding characters to match WebAuthn Specification + bytes memory challengeEncoded = bytes(Base64.encode(abi.encodePacked(_hash), true, true)); + + // The part that will old the challenge extracted from the clientData + bytes memory challengeExtracted = new bytes(challengeEncoded.length); + + assembly { + // Extract the challenge from the clientData + calldatacopy( + add(challengeExtracted, 32), add(clientData.offset, challengeOffset), mload(challengeExtracted) + ) + + // Check that the challenge extracted from the clientData is the same as the one provided in the userOp + if iszero( + eq( + // Hash of the challenge exracted from the `clientData` + keccak256(add(challengeExtracted, 32), mload(challengeExtracted)), + // Hash of the provided challenge, encoded in Base64Url (to match the clientData encoding) + keccak256(add(challengeEncoded, 32), mload(challengeEncoded)) + ) + ) { + mstore(0x00, _INVALID_WEBAUTHN_DATA_SELECTOR) + revert(0x1c, 0x04) + } + } + } + + // Verify the signature over sha256(authenticatorData || sha256(clientData)) + bytes memory verifyData = new bytes(authenticatorData.length + 32); + + assembly { + // Add the authenticator data at the start of the verifyData + calldatacopy(add(verifyData, 32), authenticatorData.offset, authenticatorData.length) + } + + bytes32 clientDataHashed = sha256(clientData); + assembly { + // Add the client data hash at the end of the verifyData + mstore(add(verifyData, add(authenticatorData.length, 32)), clientDataHashed) + } + + // Return the sha256 of the verifyData + return sha256(verifyData); + } + + /// @dev Proceed to the full webauth verification + /// @param _p256Verifier The p256 verifier contract on-chain (if user want to use this instead of the precompiled one) + /// @param _hash The hash that has been signed via WebAuthN + /// @param _signature The signature that has been provided with the userOp + /// @param _x The X point of the public key + /// @param _y The Y point of the public key + /// @return isValid True if the signature is valid, false otherwise + function _verifyWebAuthNSignature( + address _p256Verifier, + bytes32 _hash, + bytes calldata _signature, + uint256 _x, + uint256 _y + ) internal view returns (bool isValid) { + // Extract the signature + FclSignatureLayout calldata signature; + // This code should precalculate the offsets of variables as defined in the layout + // From: https://twitter.com/k06a/status/1706934230779883656 + assembly { + signature := _signature.offset + } + + // If the signature is using the on-chain p256 verifier, we will use it + if (!signature.useOnChainP256Verifier) { + _p256Verifier = PRECOMPILED_P256_VERIFIER; + } + + // Format the webauthn challenge into a p256 message + bytes32 challenge = _formatWebAuthNChallenge(_hash, signature); + + // Prepare the argument we will use to verify the signature + bytes memory args = abi.encode(challenge, signature.rs[0], signature.rs[1], _x, _y); + + // Send the call the the p256 verifier + (bool success, bytes memory ret) = _p256Verifier.staticcall(args); + // If empty ret, return false + if (success == false || ret.length == 0) { + return false; + } + + // Ensure that it has returned 1 + return abi.decode(ret, (uint256)) == 1; + } +} diff --git a/test/foundry/validator/WebAuthnFclValidator.t.sol b/test/foundry/validator/WebAuthnFclValidator.t.sol new file mode 100644 index 00000000..58e1c32b --- /dev/null +++ b/test/foundry/validator/WebAuthnFclValidator.t.sol @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IEntryPoint} from "I4337/interfaces/IEntryPoint.sol"; +import "src/Kernel.sol"; +import "forge-std/Test.sol"; +import {ERC4337Utils} from "src/utils/ERC4337Utils.sol"; +import {KernelTestBase} from "src/utils/KernelTestBase.sol"; +import {TestExecutor} from "src/mock/TestExecutor.sol"; +import {TestValidator} from "src/mock/TestValidator.sol"; +import {WebAuthnFclVerifier} from "src/validator/webauthn/WebAuthnFclVerifier.sol"; +import {P256VerifierWrapper} from "src/utils/P256VerifierWrapper.sol"; +import {WebAuthnFclValidator} from "src/validator/webauthn/WebAuthnFclValidator.sol"; +import {FCL_ecdsa_utils} from "FreshCryptoLib/FCL_ecdsa_utils.sol"; +import {Base64Url} from "FreshCryptoLib/utils/Base64Url.sol"; +import {IKernel} from "src/interfaces/IKernel.sol"; + +using ERC4337Utils for IEntryPoint; + +contract WebAuthnFclValidatorTest is KernelTestBase { + WebAuthnFclValidator private webAuthNValidator; + WebAuthNTester private webAuthNTester; + P256VerifierWrapper private p256VerifierWrapper; + + // Curve order (number of points) + uint256 constant n = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551; + + // The public key of the owner + uint256 x; + uint256 y; + + function setUp() public { + // Deploy a RIP-7212 compliant P256Verifier contract + p256VerifierWrapper = new P256VerifierWrapper(); + // Deploy a WebAuthnFclValidator contract using that RIP-7212 compliant P256Verifier contract + webAuthNValidator = new WebAuthnFclValidator(address(p256VerifierWrapper)); + + // Deploy a webAuthNTester that will be used to format the signature during test + webAuthNTester = new WebAuthNTester(); + + _initialize(); + (x, y) = _getPublicKey(ownerKey); + _setAddress(); + _setExecutionDetail(); + } + + function _setExecutionDetail() internal virtual override { + executionDetail.executor = address(new TestExecutor()); + executionSig = TestExecutor.doNothing.selector; + executionDetail.validator = new TestValidator(); + } + + function getValidatorSignature(UserOperation memory _op) internal view virtual override returns (bytes memory) { + bytes32 _hash = entryPoint.getUserOpHash(_op); + bytes memory signature = _generateWebAuthnSignature(ownerKey, _hash); + return abi.encodePacked(bytes4(0x00000000), signature); + } + + function getOwners() internal virtual override returns (address[] memory _owners) { + _owners = new address[](1); + _owners[0] = address(0); + return _owners; + } + + function getEnableData() internal view virtual override returns (bytes memory) { + return ""; + } + + function getInitializeData() internal view override returns (bytes memory) { + return abi.encodeWithSelector(KernelStorage.initialize.selector, webAuthNValidator, abi.encode(x, y)); + } + + function test_default_validator_enable() external override { + UserOperation memory op = buildUserOperation( + abi.encodeWithSelector( + IKernel.execute.selector, + address(webAuthNValidator), + 0, + abi.encodeWithSelector(webAuthNValidator.enable.selector, abi.encode(x, y)), + Operation.Call + ) + ); + performUserOperationWithSig(op); + (uint256 x2, uint256 y2) = WebAuthnFclValidator(address(webAuthNValidator)).getPublicKey(address(kernel)); + _assertPublicKey(x2, y2, x, y); + } + + function test_default_validator_disable() external override { + UserOperation memory op = buildUserOperation( + abi.encodeWithSelector( + IKernel.execute.selector, + address(webAuthNValidator), + 0, + abi.encodeWithSelector(webAuthNValidator.disable.selector, ""), + Operation.Call + ) + ); + performUserOperationWithSig(op); + (uint256 x2, uint256 y2) = WebAuthnFclValidator(address(webAuthNValidator)).getPublicKey(address(kernel)); + _assertPublicKey(x2, y2, 0, 0); + } + + function test_external_call_batch_execute_success() external override { + vm.skip(true); + } + + function test_external_call_execute_success() external override { + vm.skip(true); + } + + function test_external_call_execute_delegatecall_success() external override { + vm.skip(true); + } + + function test_external_call_execute_delegatecall_fail() external override { + vm.skip(true); + } + + function test_external_call_default() external override { + vm.skip(true); + } + + function test_external_call_execution() external override { + vm.skip(true); + } + + function test_validate_signature() external override { + bytes32 _hash = keccak256(abi.encodePacked("hello world")); + + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", ERC4337Utils._buildDomainSeparator(KERNEL_NAME, KERNEL_VERSION, address(kernel)), _hash + ) + ); + + bytes memory signature = signHash(digest); + + assertEq(kernel.isValidSignature(_hash, signature), Kernel.isValidSignature.selector); + } + + function test_fail_validate_wrongsignature() external override { + // Prepare the hash to sign + bytes32 _hash = keccak256(abi.encodePacked("hello world")); + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", ERC4337Utils._buildDomainSeparator(KERNEL_NAME, KERNEL_VERSION, address(kernel)), _hash + ) + ); + + // Sign it (via a wrong signer) + bytes memory sig = getWrongSignature(digest); + assertEq(kernel.isValidSignature(_hash, sig), bytes4(0xffffffff)); + } + + function test_fail_validate_InvalidWebAuthnData() external { + // Prepare the data to sign + bytes32 _hash = keccak256(abi.encodePacked("hello world")); + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", ERC4337Utils._buildDomainSeparator(KERNEL_NAME, KERNEL_VERSION, address(kernel)), _hash + ) + ); + + bytes32 _wrongHash = keccak256(abi.encodePacked("bye world")); + + // Sign it + bytes memory sig = signHash(digest); + + // Ensure it's reverting + vm.expectRevert(WebAuthnFclVerifier.InvalidWebAuthNData.selector); + kernel.isValidSignature(_wrongHash, sig); + } + + function signUserOp(UserOperation memory op) internal view override returns (bytes memory) { + bytes32 _hash = entryPoint.getUserOpHash(op); + bytes memory signature = _generateWebAuthnSignature(ownerKey, _hash); + return abi.encodePacked(bytes4(0x00000000), signature); + } + + function getWrongSignature(UserOperation memory op) internal view override returns (bytes memory) { + bytes32 _hash = entryPoint.getUserOpHash(op); + bytes memory signature = _generateWebAuthnSignature(ownerKey + 1, _hash); + return abi.encodePacked(bytes4(0x00000000), signature); + } + + function signHash(bytes32 _hash) internal view override returns (bytes memory) { + return _generateWebAuthnSignature(ownerKey, _hash); + } + + function getWrongSignature(bytes32 _hash) internal view override returns (bytes memory) { + return _generateWebAuthnSignature(ownerKey + 1, _hash); + } + + function _assertPublicKey(uint256 actualX, uint256 actualY, uint256 expectedX, uint256 expectedY) internal { + assertEq(actualX, expectedX, "Public key X component mismatch"); + assertEq(actualY, expectedY, "Public key Y component mismatch"); + } + + /// @dev Ensure that the validation won't revert when using the dummy signature bypass (challenge offset to uint256.max) + function test_dontRevertForDummySig() public { + // Build rly dummy data for authenticator data and client data + bytes memory authenticatorData = hex"1312"; + bytes memory clientData = hex"1312"; + // Set the client challenge data offset to the max value + uint256 clientChallengeDataOffset = type(uint256).max; + + // Build an incoherent signature + uint256[2] memory rs = [type(uint256).max, type(uint256).max]; + + // Encode all of that into a signature + bytes memory signature = abi.encode(true, authenticatorData, clientData, clientChallengeDataOffset, rs); + + // Check the sig (and ensure we didn't revert here) + bool isValid = webAuthNTester.verifySignature(address(p256VerifierWrapper), bytes32(0), signature, x, y); + assertEq(isValid, false); + + // Ensure we can go through the validator with that signature + ValidationData validationData = webAuthNValidator.validateSignature(bytes32(0), signature); + assertEq(ValidationData.unwrap(validationData), 1); + } + + /// @dev Ensure that our flow to generate a webauthn signature is working + function test_webAuthnSignatureGeneration(bytes32 _hash, uint256 _privateKey) public { + vm.assume(_privateKey > 0); + vm.assume(_privateKey < n); + (uint256 pubX, uint256 pubY) = _getPublicKey(_privateKey); + + // Build all the data required + (bytes32 msgToSign, bytes memory authenticatorData, bytes memory clientData, uint256 clientChallengeDataOffset) + = _prepapreWebAuthnMsg(_hash); + + // Then sign them + (uint256 r, uint256 s) = _getP256Signature(_privateKey, msgToSign); + uint256[2] memory rs = [r, s]; + + // Encode all of that into a signature + bytes memory signature = abi.encode(true, authenticatorData, clientData, clientChallengeDataOffset, rs); + + // Ensure the signature is valid + bool isValid = webAuthNTester.verifySignature(address(p256VerifierWrapper), _hash, signature, pubX, pubY); + assertEq(isValid, true); + } + + /// @dev Ensure that our flow to generate a webauthn signature is working + function test_webAuthnSignatureGeneration_solo() public { + uint256 _privateKey = 0x1; + bytes32 _hash = keccak256(abi.encodePacked("hello world")); + (uint256 pubX, uint256 pubY) = _getPublicKey(_privateKey); + + // Build all the data required + (bytes32 msgToSign, bytes memory authenticatorData, bytes memory clientData, uint256 clientChallengeDataOffset) + = _prepapreWebAuthnMsg(_hash); + + // Then sign them + (uint256 r, uint256 s) = _getP256Signature(_privateKey, msgToSign); + uint256[2] memory rs = [r, s]; + + // Encode all of that into a signature + bytes memory signature = abi.encode(true, authenticatorData, clientData, clientChallengeDataOffset, rs); + + // Ensure the signature is valid + bool isValid = webAuthNTester.verifySignature(address(p256VerifierWrapper), _hash, signature, pubX, pubY); + assertEq(isValid, true); + } + + /* -------------------------------------------------------------------------- */ + /* Signature & P256 helper functions */ + /* -------------------------------------------------------------------------- */ + + /// @dev Generate a webauthn signature for the given `_hash` using the given `_privateKey` + function _generateWebAuthnSignature(uint256 _privateKey, bytes32 _hash) + internal + view + returns (bytes memory signature) + { + (bytes32 msgToSign, bytes memory authenticatorData, bytes memory clientData, uint256 clientChallengeDataOffset) + = _prepapreWebAuthnMsg(_hash); + + // Get the signature + (uint256 r, uint256 s) = _getP256Signature(_privateKey, msgToSign); + uint256[2] memory rs = [r, s]; + + // Return the signature + return abi.encode(true, authenticatorData, clientData, clientChallengeDataOffset, rs); + } + + /// @dev Prepare all the base data needed to perform a webauthn signature o n the given `_hash` + function _prepapreWebAuthnMsg(bytes32 _hash) + internal + view + returns ( + bytes32 msgToSign, + bytes memory authenticatorData, + bytes memory clientData, + uint256 clientChallengeDataOffset + ) + { + // Base Mapping of the message + bytes memory encodedChallenge = bytes(Base64Url.encode(abi.encodePacked(_hash))); + + // Prepare the authenticator data (from a real webauthn challenge) + authenticatorData = hex"49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97630500000000"; + + // Prepare the client data (starting from a real webauthn challenge, then replacing only the bytes needed for the challenge) + bytes memory clientDataStart = hex"7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22"; + bytes memory clientDataEnd = + hex"222c226f726967696e223a22687474703a2f2f6c6f63616c686f73743a33303032222c2263726f73734f726967696e223a66616c73657d"; + clientData = bytes.concat(clientDataStart, encodedChallenge, clientDataEnd); + clientChallengeDataOffset = 36; + + // Build the signature layout + WebAuthnFclVerifier.FclSignatureLayout memory sigLayout = WebAuthnFclVerifier.FclSignatureLayout({ + useOnChainP256Verifier: true, + authenticatorData: authenticatorData, + clientData: clientData, + challengeOffset: clientChallengeDataOffset, + // R/S not needed since the formatter will only use the other data + rs: [uint256(0), uint256(0)] + }); + + // Format it + msgToSign = webAuthNTester.formatSigLayout(_hash, sigLayout); + } + + /// @dev Get a public key for a p256 user, from the given `_privateKey` + function _getPublicKey(uint256 _privateKey) internal view returns (uint256, uint256) { + return FCL_ecdsa_utils.ecdsa_derivKpub(_privateKey); + } + + /// P256 curve order n/2 for malleability check + uint256 constant P256_N_DIV_2 = 57896044605178124381348723474703786764998477612067880171211129530534256022184; + + /// @dev Generate a p256 signature, from the given `_privateKey` on the given `_hash` + function _getP256Signature(uint256 _privateKey, bytes32 _hash) internal pure returns (uint256, uint256) { + // Generate the signature using the k value and the private key + (bytes32 r, bytes32 s) = vm.signP256(_privateKey, _hash); + return (uint256(r), uint256(s)); + } +} + +/// @dev simple contract to format a webauthn challenge (using to convert stuff in memory during test to calldata) +contract WebAuthNTester { + function formatSigLayout(bytes32 _hash, WebAuthnFclVerifier.FclSignatureLayout calldata signatureLayout) + public + view + returns (bytes32) + { + console.log("hash: %d", uint256(_hash)); + return WebAuthnFclVerifier._formatWebAuthNChallenge(_hash, signatureLayout); + } + + function verifySignature(address _p256Verifier, bytes32 _hash, bytes calldata _signature, uint256 _x, uint256 _y) + public + view + returns (bool) + { + return WebAuthnFclVerifier._verifyWebAuthNSignature(_p256Verifier, _hash, _signature, _x, _y); + } +} diff --git a/test/foundry/validator/WeightedECDSAValidator.t.sol b/test/foundry/validator/WeightedECDSAValidator.t.sol new file mode 100644 index 00000000..b6e96853 --- /dev/null +++ b/test/foundry/validator/WeightedECDSAValidator.t.sol @@ -0,0 +1,169 @@ +pragma solidity ^0.8.0; + +import {IEntryPoint} from "I4337/interfaces/IEntryPoint.sol"; +import "src/Kernel.sol"; +import "src/validator/WeightedECDSAValidator.sol"; +// test artifacts +// test utils +import "forge-std/Test.sol"; +import {ERC4337Utils} from "src/utils/ERC4337Utils.sol"; +import {KernelTestBase} from "src/utils/KernelTestBase.sol"; +import {TestExecutor} from "src/mock/TestExecutor.sol"; +import {TestValidator} from "src/mock/TestValidator.sol"; +import {IKernel} from "src/interfaces/IKernel.sol"; + +using ERC4337Utils for IEntryPoint; + +contract KernelWeightedECDSATest is KernelTestBase { + address[] public owners; + uint256[] public ownerKeys; + uint24[] public weights; + uint24 public threshold; + uint48 public delay; + + function setUp() public virtual { + _initialize(); + defaultValidator = new WeightedECDSAValidator(); + owners = new address[](3); + ownerKeys = new uint256[](3); + (owners[0], ownerKeys[0]) = makeAddrAndKey("owner0"); + (owners[1], ownerKeys[1]) = makeAddrAndKey("owner1"); + (owners[2], ownerKeys[2]) = makeAddrAndKey("owner2"); + // sort owners and keys from largest to smallest owner address + for (uint256 i = 0; i < owners.length; i++) { + for (uint256 j = i + 1; j < owners.length; j++) { + if (owners[i] < owners[j]) { + address tempAddr = owners[i]; + owners[i] = owners[j]; + owners[j] = tempAddr; + uint256 tempKey = ownerKeys[i]; + ownerKeys[i] = ownerKeys[j]; + ownerKeys[j] = tempKey; + } + } + } + weights = [uint24(1), uint24(2), uint24(3)]; + threshold = 3; + delay = 0; + _setAddress(); + _setExecutionDetail(); + } + + function test_ignore() external {} + + function _setExecutionDetail() internal virtual override { + executionDetail.executor = address(new TestExecutor()); + executionSig = TestExecutor.doNothing.selector; + executionDetail.validator = new TestValidator(); + } + + function getEnableData() internal view virtual override returns (bytes memory) { + return ""; + } + + function getValidatorSignature(UserOperation memory) internal view virtual override returns (bytes memory) { + return ""; + } + + function getOwners() internal view override returns (address[] memory) { + return owners; + } + + function getInitializeData() internal view override returns (bytes memory) { + bytes memory data = abi.encode(owners, weights, threshold, delay); + return abi.encodeWithSelector(KernelStorage.initialize.selector, defaultValidator, data); + } + + function test_external_call_execute_success() external override { + vm.skip(true); + } + + function test_external_call_default() external override { + vm.skip(true); + } + + function test_external_call_execute_delegatecall_success() external override { + vm.skip(true); + } + + function test_external_call_batch_execute_success() external override { + vm.skip(true); + } + + function signUserOp(UserOperation memory op) internal view override returns (bytes memory) { + bytes32 calldataAndNonceHash = keccak256(abi.encode(op.sender, op.callData, op.nonce)); + + bytes32 digest = keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256("WeightedECDSAValidator"), + keccak256("0.0.3"), + block.chainid, + address(defaultValidator) + ) + ); + + bytes32 structHash = + keccak256(abi.encode(keccak256("Approve(bytes32 callDataAndNonceHash)"), calldataAndNonceHash)); + assembly { + // Compute the digest. + mstore(0x00, 0x1901000000000000) // Store "\x19\x01". + mstore(0x1a, digest) // Store the domain separator. + mstore(0x3a, structHash) // Store the struct hash. + digest := keccak256(0x18, 0x42) + // Restore the part of the free memory slot that was overwritten. + mstore(0x3a, 0) + } + + (uint8 v0, bytes32 r0, bytes32 s0) = vm.sign(ownerKeys[0], digest); + (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(ownerKeys[1], digest); + bytes memory opSig = entryPoint.signUserOpHash(vm, ownerKeys[2], op); + return abi.encodePacked(bytes4(0x00000000), r0, s0, v0, r1, s1, v1, opSig); + } + + function getWrongSignature(UserOperation memory op) internal view override returns (bytes memory) { + return abi.encodePacked(bytes4(0x00000000), entryPoint.signUserOpHash(vm, ownerKeys[0], op)); + } + + function signHash(bytes32 hash) internal view override returns (bytes memory) { + (uint8 v0, bytes32 r0, bytes32 s0) = vm.sign(ownerKeys[0], hash); + (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(ownerKeys[1], hash); + (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(ownerKeys[2], hash); + return abi.encodePacked(r0, s0, v0, r1, s1, v1, r2, s2, v2); + } + + function getWrongSignature(bytes32 hash) internal view override returns (bytes memory) { + (uint8 v0, bytes32 r0, bytes32 s0) = vm.sign(ownerKeys[1], hash); + (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(ownerKeys[0], hash); + (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(ownerKeys[2], hash); + return abi.encodePacked(r0, s0, v0, r1, s1, v1, r2, s2, v2); + } + + function test_default_validator_enable() external override { + //UserOperation memory op = buildUserOperation( + // abi.encodeWithSelector( + // IKernel.execute.selector, + // address(defaultValidator), + // 0, + // abi.encodeWithSelector(ECDSAValidator.enable.selector, abi.encodePacked(address(0xdeadbeef))), + // Operation.Call + // ) + //); + //performUserOperationWithSig(op); + //(address owner) = ECDSAValidator(address(defaultValidator)).ecdsaValidatorStorage(address(kernel)); + //assertEq(owner, address(0xdeadbeef), "owner should be 0xdeadbeef"); + } + + function test_default_validator_disable() external override { + UserOperation memory op = buildUserOperation( + abi.encodeWithSelector( + IKernel.execute.selector, + address(defaultValidator), + 0, + abi.encodeWithSelector(WeightedECDSAValidator.disable.selector, ""), + Operation.Call + ) + ); + performUserOperationWithSig(op); + } +}