-
Notifications
You must be signed in to change notification settings - Fork 20.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
accounts, signer: implement gnosis safe support #21593
Changes from 6 commits
29691ed
5a9ac05
22857d2
f72e4cc
346b8fa
f09e3de
8f8af8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,7 +41,7 @@ const ( | |
// numberOfAccountsToDerive For hardware wallets, the number of accounts to derive | ||
numberOfAccountsToDerive = 10 | ||
// ExternalAPIVersion -- see extapi_changelog.md | ||
ExternalAPIVersion = "6.0.0" | ||
ExternalAPIVersion = "6.1.0" | ||
// InternalAPIVersion -- see intapi_changelog.md | ||
InternalAPIVersion = "7.0.1" | ||
) | ||
|
@@ -62,6 +62,8 @@ type ExternalAPI interface { | |
EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) | ||
// Version info about the APIs | ||
Version(ctx context.Context) (string, error) | ||
// SignGnosisSafeTransaction signs/confirms a gnosis-safe multisig transaction | ||
SignGnosisSafeTx(ctx context.Context, signerAddress common.MixedcaseAddress, gnosisTx GnosisSafeTx, methodSelector *string) (*GnosisSafeTx, error) | ||
} | ||
|
||
// UIClientAPI specifies what method a UI needs to implement to be able to be used as a | ||
|
@@ -234,6 +236,7 @@ type ( | |
Address common.MixedcaseAddress `json:"address"` | ||
Rawdata []byte `json:"raw_data"` | ||
Messages []*NameValueType `json:"messages"` | ||
Callinfo []ValidationInfo `json:"call_info"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this doesn't seem to be used anywhere |
||
Hash hexutil.Bytes `json:"hash"` | ||
Meta Metadata `json:"meta"` | ||
} | ||
|
@@ -581,6 +584,33 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth | |
|
||
} | ||
|
||
func (api *SignerAPI) SignGnosisSafeTx(ctx context.Context, signerAddress common.MixedcaseAddress, gnosisTx GnosisSafeTx, methodSelector *string) (*GnosisSafeTx, error) { | ||
// Do the usual validations, but on the last-stage transaction | ||
args := gnosisTx.ArgsForValidation() | ||
msgs, err := api.validator.ValidateTransaction(methodSelector, args) | ||
if err != nil { | ||
return nil, err | ||
} | ||
holiman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// If we are in 'rejectMode', then reject rather than show the user warnings | ||
if api.rejectMode { | ||
if err := msgs.getWarnings(); err != nil { | ||
return nil, err | ||
} | ||
} | ||
typedData := gnosisTx.ToTypedData() | ||
signature, preimage, err := api.signTypedData(ctx, signerAddress, typedData, msgs) | ||
if err != nil { | ||
return nil, err | ||
} | ||
checkSummedSender, _ := common.NewMixedcaseAddressFromString(signerAddress.Address().Hex()) | ||
|
||
gnosisTx.Signature = signature | ||
gnosisTx.SafeTxHash = common.BytesToHash(preimage) | ||
gnosisTx.Sender = *checkSummedSender // Must be checksumed to be accepted by relay | ||
|
||
return &gnosisTx, nil | ||
} | ||
|
||
// Returns the external api version. This method does not require user acceptance. Available methods are | ||
// available via enumeration anyway, and this info does not contain user-specific data | ||
func (api *SignerAPI) Version(ctx context.Context) (string, error) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package core | ||
|
||
import ( | ||
"fmt" | ||
"math/big" | ||
|
||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/common/hexutil" | ||
"github.com/ethereum/go-ethereum/common/math" | ||
) | ||
|
||
// GnosisSafeTx is a type to parse the safe-tx returned by the relayer, | ||
// it also conforms to the API required by the Gnosis Safe tx relay service. | ||
// See 'SafeMultisigTransaction' on https://safe-transaction.mainnet.gnosis.io/ | ||
type GnosisSafeTx struct { | ||
// These fields are only used on output | ||
Signature hexutil.Bytes `json:"signature"` | ||
SafeTxHash common.Hash `json:"contractTransactionHash"` | ||
Sender common.MixedcaseAddress `json:"sender"` | ||
// These fields are used both on input and output | ||
Safe common.MixedcaseAddress `json:"safe"` | ||
To common.MixedcaseAddress `json:"to"` | ||
Value math.Decimal256 `json:"value"` | ||
GasPrice math.Decimal256 `json:"gasPrice"` | ||
Data *hexutil.Bytes `json:"data"` | ||
Operation uint8 `json:"operation"` | ||
GasToken common.Address `json:"gasToken"` | ||
RefundReceiver common.Address `json:"refundReceiver"` | ||
BaseGas big.Int `json:"baseGas"` | ||
SafeTxGas big.Int `json:"safeTxGas"` | ||
Nonce big.Int `json:"nonce"` | ||
InputExpHash common.Hash `json:"safeTxHash"` | ||
} | ||
|
||
// ToTypedData converts the tx to a EIP-712 Typed Data structure for signing | ||
func (tx *GnosisSafeTx) ToTypedData() TypedData { | ||
var data hexutil.Bytes | ||
if tx.Data != nil { | ||
data = *tx.Data | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is not being tested There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean this line in particular or unit-testing of this file in general? |
||
} | ||
gnosisTypedData := TypedData{ | ||
Types: Types{ | ||
"EIP712Domain": []Type{{Name: "verifyingContract", Type: "address"}}, | ||
"SafeTx": []Type{ | ||
{Name: "to", Type: "address"}, | ||
{Name: "value", Type: "uint256"}, | ||
{Name: "data", Type: "bytes"}, | ||
{Name: "operation", Type: "uint8"}, | ||
{Name: "safeTxGas", Type: "uint256"}, | ||
{Name: "baseGas", Type: "uint256"}, | ||
{Name: "gasPrice", Type: "uint256"}, | ||
{Name: "gasToken", Type: "address"}, | ||
{Name: "refundReceiver", Type: "address"}, | ||
{Name: "nonce", Type: "uint256"}, | ||
}, | ||
}, | ||
Domain: TypedDataDomain{ | ||
VerifyingContract: tx.Safe.Address().Hex(), | ||
}, | ||
PrimaryType: "SafeTx", | ||
Message: TypedDataMessage{ | ||
"to": tx.To.Address().Hex(), | ||
"value": tx.Value.String(), | ||
"data": data, | ||
"operation": fmt.Sprintf("%d", tx.Operation), | ||
"safeTxGas": fmt.Sprintf("%#d", &tx.SafeTxGas), | ||
"baseGas": fmt.Sprintf("%#d", &tx.BaseGas), | ||
"gasPrice": tx.GasPrice.String(), | ||
"gasToken": tx.GasToken.Hex(), | ||
"refundReceiver": tx.RefundReceiver.Hex(), | ||
"nonce": fmt.Sprintf("%d", tx.Nonce.Uint64()), | ||
}, | ||
} | ||
return gnosisTypedData | ||
} | ||
|
||
// ArgsForValidation returns a SendTxArgs struct, which can be used for the | ||
// common validations, e.g. look up 4byte destinations | ||
func (tx *GnosisSafeTx) ArgsForValidation() *SendTxArgs { | ||
args := &SendTxArgs{ | ||
From: tx.Safe, | ||
To: &tx.To, | ||
Gas: hexutil.Uint64(tx.SafeTxGas.Uint64()), | ||
GasPrice: hexutil.Big(tx.GasPrice), | ||
Value: hexutil.Big(tx.Value), | ||
Nonce: hexutil.Uint64(tx.Nonce.Uint64()), | ||
Data: tx.Data, | ||
Input: nil, | ||
} | ||
return args | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't seem to be used anywhere
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right... I added that there, then converted all validation-messags into NVT messages instead, having forgotten about that^ . I'll use that instead