1

I have a gnosis safe which I am interacting with directly (no sdk here). It has a threshold of 2 and 3 signers. I am attempting to execute a txn by calling execTransaction() directly. It is failing with the GS026 error for incorrectly formatted signatures, but when I call checkNsignatures directly with the same inputs, it doesn't fail.

My script is written in python using a combination of web3 & apeWorx

First I get the txn_hash with:

txn_hash = my_safe_master_abi.getTransactionHash(
    to,
    value,
    calldata.data,
    operation,
    safeTxGas,
    baseGas,
    gasPrice,
    gasToken,
    refundReceiver,
    nonce
)

I then sign the txn hash and convert the signature as described in the gnosis docs

from web3.auto import w3
from eth_account.messages import encode_defunct

encoded_hash = encode_defunct(txn_hash)
signed_message1 = w3.eth.account.sign_message(encoded_hash, private_key=owner1.private_key)
ecdsa_signature1 = hex(signed_message1.r) + hex(signed_message1.s)[2:] + hex(signed_message1.v + 4)[2:]

I repeat this process with the 2 other owners to create 3 signatures.

ecdsa_signature2 = hex(signed_message2.r) + hex(signed_message2.s)[2:] + hex(signed_message2.v + 4)[2:]
ecdsa_signature3 = hex(signed_message3.r) + hex(signed_message3.s)[2:] + hex(signed_message3.v + 4)[2:]

To test that the individual signatures work, I call checkNsignatures with the threshold set to 1. This way I can test if the signatures are formatted properly individually before concatenating. my_safe_master_abi.checkNSignatures(txnHash, datahash, ecdsa_signature1, threshold)

Strangely, the checkNsignatures successfully executes for signatures 1 & 3 but not for 2

I continued with signatures 1 and 3 and concatenated them, also per the docs, checking that they are in ascending order: signatures = ecda_signature1 + ecda_signature3[2:] and called checkNsignatures successfully

When I then call my_safe_master_abi.execTransaction() with the same signatures variable, it fails and throws error GS026 which is inside of the checkNsignatures function which passed when called directly

Would appreciate help on both why signature 2 is failing separately from 1 & 3 AND why checkNsignatures is failing when called within execTransaction

Full source code (note: local hardhat chain with a fork of eth mainnet). Not really calling this as a script, but executing line by line in ape console to sort out where I'm going wrong

txn_hash = my_safe_master_abi.getTransactionHash(
    to,
    value,
    calldata.data,
    operation,
    safeTxGas,
    baseGas,
    gasPrice,
    gasToken,
    refundReceiver,
    nonce
)

from web3.auto import w3
from eth_account.messages import encode_defunct

encoded_hash = encode_defunct(txn_hash)
signed_message1 = w3.eth.account.sign_message(encoded_hash, private_key=owner1.private_key)
signed_message2 = w3.eth.account.sign_message(encoded_hash, private_key=owner2.private_key)
signed_message3 = w3.eth.account.sign_message(encoded_hash, private_key=owner3.private_key)

## signatures1 is working
## >>> '0xf4f681475a9a03ef457c2fcf85e82f52872fc3c7ad10dd74245e6e898ad0c8d84c441b7adea575f5e10ccf3d25d80fa7efc878078ded98995ca1fa7be70a8e9720'
ecdsa_signature1 = hex(signed_message1.r) + hex(signed_message1.s)[2:] + hex(signed_message1.v + 4)[2:]
## signatures2 is not working
## >>> 0x15c81d3a62b1635e4dedb594e2b009e6943498d83cf38a89c3efaf819fc855e7a8f8cd3a13968de8ed8718c05af49e26c9b871c42325e91bb0d344b3cfdeb341f
ecdsa_signature2 = hex(signed_message2.r) + hex(signed_message2.s)[2:] + hex(signed_message2.v + 4)[2:]
## signature3 is working
## >>> 0x1183ab034b94fb9ff69e3a81abc057e23485bd2b8766491d639526367ac19a4727dd0235c6b948cd9fd3d35b436b06358f8d2e1d481c4fd544ae5f20b510fb6b20
ecdsa_signature3 = hex(signed_message3.r) + hex(signed_message3.s)[2:] + hex(signed_message3.v + 4)[2:]

## >>>'0xf4f681475a9a03ef457c2fcf85e82f52872fc3c7ad10dd74245e6e898ad0c8d84c441b7adea575f5e10ccf3d25d80fa7efc878078ded98995ca1fa7be70a8e97201183ab034b94fb9ff69e3a81abc057e23485bd2b8766491d639526367ac19a4727dd0235c6b948cd9fd3d35b436b06358f8d2e1d481c4fd544ae5f20b510fb6b20'
signatures = ecdsa_signature1 + ecdsa_signature3[2:]

## Check signature with checkN signatures
## need encoded dat
datahash = my_safe_master_abi.encodeTransactionData(
    to,
    value,
    calldata.data,
    operation,
    safeTxGas,
    baseGas,
    gasPrice,
    gasToken,
    refundReceiver,
    nonce
)

## need hashed data
txnHash = my_safe_master_abi.getTransactionHash(
    to,
    value,
    calldata.data,
    operation,
    safeTxGas,
    baseGas,
    gasPrice,
    gasToken,
    refundReceiver,
    nonce
)

## Set threshold to 1 to check individual signatures
threshold = 1

## check signature, works with signature 1, 3, & 1+3
my_safe_master_abi.checkNSignatures(txnHash, datahash, ecdsa_signature1, threshold)

theshold = 2
my_safe_master_abi.checkNSignatures(txnHash, datahash, signatures, threshold)


## try to execute a transaction
my_safe_master_abi.execTransaction(
    to,
    value,
    calldata.data,
    operation,
    safeTxGas,
    baseGas,
    gasPrice,
    gasToken,
    refundReceiver,
    signatures,
    sender = owner1
)

2
  • Solved my first question. The s value of the ecdsa_signature2 was one number too short. Left padding it with an extra 0 solved that issue. So now all thats left to sort out is why its failing when I call execTransaction
    – Eric Brown
    Commented Oct 13, 2022 at 19:50
  • Gnosis safe splits the signature into v,s,r. It then checks if v is > 30 and then regenerates the signer address from v,s,r and the txnHash. I have stepped through this on my own by duplicating pieces of the gnosis safe contract and have verified that my signatures all generate v>30 and that the generated address is == the signer address. Starting to run out of options for where this is going wrong...
    – Eric Brown
    Commented Oct 13, 2022 at 20:45

1 Answer 1

1

If checkNSignature works, but execTransaction doesn't, there could be only one reason: the transaction hash differs from the one you calculated and signed. Ensure you're passing a correct nonce to the getTransactionHash.

1
  • 1
    This is correct. the my_safe.nonce for some reason isn't returning the correct nonce. When I code manually, it does actually work. Thanks for the tip! Free from banging my head against the wall on this one
    – Eric Brown
    Commented Oct 14, 2022 at 18:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.