Created
October 1, 2024 03:59
-
-
Save mingderwang/07868c4c06c40c811c007ba597a75462 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.26; | |
// OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/ECDSA.sol) | |
library ECDSA { | |
enum RecoverError { | |
NoError, | |
InvalidSignature, | |
InvalidSignatureLength, | |
InvalidSignatureS, | |
InvalidSignatureV | |
} | |
function _throwError(RecoverError error) private pure { | |
if (error == RecoverError.NoError) { | |
return; // no error: do nothing | |
} else if (error == RecoverError.InvalidSignature) { | |
revert("ECDSA: invalid signature"); | |
} else if (error == RecoverError.InvalidSignatureLength) { | |
revert("ECDSA: invalid signature length"); | |
} else if (error == RecoverError.InvalidSignatureS) { | |
revert("ECDSA: invalid signature 's' value"); | |
} else if (error == RecoverError.InvalidSignatureV) { | |
revert("ECDSA: invalid signature 'v' value"); | |
} | |
} | |
function tryRecover(bytes32 hash, bytes memory signature) | |
internal | |
pure | |
returns (address, RecoverError) | |
{ | |
// Check the signature length | |
// - case 65: r,s,v signature (standard) | |
// - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._ | |
if (signature.length == 65) { | |
bytes32 r; | |
bytes32 s; | |
uint8 v; | |
// ecrecover takes the signature parameters, and the only way to get them | |
// currently is to use assembly. | |
assembly { | |
r := mload(add(signature, 0x20)) | |
s := mload(add(signature, 0x40)) | |
v := byte(0, mload(add(signature, 0x60))) | |
} | |
return tryRecover(hash, v, r, s); | |
} else if (signature.length == 64) { | |
bytes32 r; | |
bytes32 vs; | |
// ecrecover takes the signature parameters, and the only way to get them | |
// currently is to use assembly. | |
assembly { | |
r := mload(add(signature, 0x20)) | |
vs := mload(add(signature, 0x40)) | |
} | |
return tryRecover(hash, r, vs); | |
} else { | |
return (address(0), RecoverError.InvalidSignatureLength); | |
} | |
} | |
function recover(bytes32 hash, bytes memory signature) | |
internal | |
pure | |
returns (address) | |
{ | |
(address recovered, RecoverError error) = tryRecover(hash, signature); | |
_throwError(error); | |
return recovered; | |
} | |
function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) | |
internal | |
pure | |
returns (address, RecoverError) | |
{ | |
bytes32 s = vs | |
& bytes32( | |
0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff | |
); | |
uint8 v = uint8((uint256(vs) >> 255) + 27); | |
return tryRecover(hash, v, r, s); | |
} | |
function recover(bytes32 hash, bytes32 r, bytes32 vs) | |
internal | |
pure | |
returns (address) | |
{ | |
(address recovered, RecoverError error) = tryRecover(hash, r, vs); | |
_throwError(error); | |
return recovered; | |
} | |
function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) | |
internal | |
pure | |
returns (address, RecoverError) | |
{ | |
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature | |
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines | |
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most | |
// signatures from current libraries generate a unique signature with an s-value in the lower half order. | |
// | |
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value | |
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or | |
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept | |
// these malleable signatures as well. | |
if ( | |
uint256(s) | |
> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 | |
) { | |
return (address(0), RecoverError.InvalidSignatureS); | |
} | |
if (v != 27 && v != 28) { | |
return (address(0), RecoverError.InvalidSignatureV); | |
} | |
// If the signature is valid (and not malleable), return the signer address | |
address signer = ecrecover(hash, v, r, s); | |
if (signer == address(0)) { | |
return (address(0), RecoverError.InvalidSignature); | |
} | |
return (signer, RecoverError.NoError); | |
} | |
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) | |
internal | |
pure | |
returns (address) | |
{ | |
(address recovered, RecoverError error) = tryRecover(hash, v, r, s); | |
_throwError(error); | |
return recovered; | |
} | |
function toEthSignedMessageHash(bytes32 hash) | |
internal | |
pure | |
returns (bytes32) | |
{ | |
// 32 is the length in bytes of hash, | |
// enforced by the type signature above | |
return keccak256( | |
abi.encodePacked("\x19Ethereum Signed Message:\n32", hash) | |
); | |
} | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.26; | |
import "./ECDSA.sol"; | |
contract MultiSigWallet { | |
using ECDSA for bytes32; | |
address[2] public owners; | |
mapping(bytes32 => bool) public executed; | |
constructor(address[2] memory _owners) payable { | |
owners = _owners; | |
} | |
function deposit() external payable {} | |
function transfer( | |
address _to, | |
uint256 _amount, | |
uint256 _nonce, | |
bytes[2] memory _sigs | |
) external { | |
bytes32 txHash = getTxHash(_to, _amount, _nonce); | |
require(!executed[txHash], "tx executed"); | |
require(_checkSigs(_sigs, txHash), "invalid sig"); | |
executed[txHash] = true; | |
(bool sent,) = _to.call{value: _amount}(""); | |
require(sent, "Failed to send Ether"); | |
} | |
function getTxHash(address _to, uint256 _amount, uint256 _nonce) | |
public | |
view | |
returns (bytes32) | |
{ | |
return keccak256(abi.encodePacked(address(this), _to, _amount, _nonce)); | |
} | |
function _checkSigs(bytes[2] memory _sigs, bytes32 _txHash) | |
private | |
view | |
returns (bool) | |
{ | |
bytes32 ethSignedHash = _txHash.toEthSignedMessageHash(); | |
for (uint256 i = 0; i < _sigs.length; i++) { | |
address signer = ethSignedHash.recover(_sigs[i]); | |
bool valid = signer == owners[i]; | |
if (!valid) { | |
return false; | |
} | |
} | |
return true; | |
} | |
} | |
/* | |
// owners | |
0xe19aea93F6C1dBef6A3776848bE099A7c3253ac8 | |
0xfa854FE5339843b3e9Bfd8554B38BD042A42e340 | |
// to | |
0xe10422cc61030C8B3dBCD36c7e7e8EC3B527E0Ac | |
// amount | |
100 | |
// nonce | |
0 | |
// tx hash | |
0x12a095462ebfca27dc4d99feef885bfe58344fb6bb42c3c52a7c0d6836d11448 | |
// signatures | |
0x120f8ed8f2fa55498f2ef0a22f26e39b9b51ed29cc93fe0ef3ed1756f58fad0c6eb5a1d6f3671f8d5163639fdc40bb8720de6d8f2523077ad6d1138a60923b801c | |
0xa240a487de1eb5bb971e920cb0677a47ddc6421e38f7b048f8aa88266b2c884a10455a52dc76a203a1a9a953418469f9eec2c59e87201bbc8db0e4d9796935cb1b | |
*/ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment