I’m trying to test my ecdsa singing locally.
I’m generating my public key using ecdsa_public_key with a Hash that I know is stable that that I use as my derivation path later when I try to sign.
let address = await server.ecdsa_public_key({
canister_id = ?canister;
derivation_path = [sha];
key_id = {curve = #secp256k1; name = state.settings.tecdsaKeyName};
});
let #ok(evm_address) = EVMAddress.fromPublicKey(encodedPk) else {
return null;
};
I’ve checked that this key to address verification works using Ethereum Public Key To Address Online
It seems to be working.
Later when I want to sign I derive the proper hash and do the following:
let inputList = Buffer.Buffer<RLPTypes.Input>(1);
inputList.add(#number(thisNonce));
inputList.add(#number(gasPrice));
inputList.add(#number(gasLimit));
inputList.add(#string(Text.toLowercase(pointer.contract)));
inputList.add(#number(0));
inputList.add(#Uint8Array(abi));
inputList.add(#number(chainId));
inputList.add(#number(0));
inputList.add(#number(0));
I take a a keccak of this abi set up and sign it with my tecdsa key with the same derivation path(I’ve checked and double checked this).
let { signature } = await server.sign_with_ecdsa({
message_hash = Blob.fromArray(msgToSign);
derivation_path = [sha];
key_id = {curve = #secp256k1; name = state.settings.tecdsaKeyName};
});
I get a signature which I assume are r and s.
I wasted a day trying to derive v (no clue why we don’t get this as part of the key) and I’m just firing both options for v off at the final rpc. Something along these lines:
let r = Array.take<Nat8>(sigBytes, 32);
let s = Array.take<Nat8>(sigBytes, -32);
var v = if(recoveryId == 0){
(chainId *2) + 35;
} else {
(chainId *2) + 36;
};
debug if(debug_channel.announce) D.print(debug_show(("Ethereum Sig", v, r, s)));
let inputList2 = Buffer.Buffer<RLPTypes.Input>(1);
inputList2.add(#number(thisNonce));
inputList2.add(#number(gasPrice));
inputList2.add(#number(gasLimit));
inputList2.add(#string(Text.toLowercase(pointer.contract)));
inputList2.add(#number(0));
inputList2.add(#Uint8Array(abi));
inputList2.add(#number(v));
inputList2.add(#Uint8Array(Buffer.fromArray<Nat8>(r)));
inputList2.add(#Uint8Array(Buffer.fromArray<Nat8>(s)));
let #ok(rlpPost) = RLP.encode(#List(inputList2)) else {
return (#err(#GenericError("Failed to encode RLP")), state.cycleSettings.baseCharge);
};
let rlpText = Hex.encode(Buffer.toArray(rlpPost));
Then I ship this off to the rpc hoping that all will be well, but I get an error that my address has no eth although I’ve sent it much eth.
I’m pretty sure I have the contract set up correctly because I can plug
0xf8cc8083140eca85e8d4a51000949fe46736679d2d9a65f0992f2272de9f3c7fa6e080b86442842e0e00000000000000000000000065315b4df3ebaf8738507b1ce295bf473e31fcdf000000000000000000000000974a5c78a5cd3b9ad38883f0a8301bb5968b5efd000000000000000000000000000000000000000000000000000000000000001f82f4f6a0b53364bd58b72b3117b87010651edf6c118108025c0b40a04c3e18fc7e9f2905a03315f551aaaf5392470126d3b60536dee038f088f1015b0e894a5cc65cc9e2d3
into MyCrypto - Ethereum Wallet Manager and I see a pretty valid transaction. The only thing that is wrong is the from address. This is not the address I sent the eth to and it is not the address derived by the key I pulled from ecdsa_public_key.
Maybe this just doesn’t work locally? I don’t think that is the case? Have I made a poor assumption about the format of the signature that comes back(32 bytes of r then 32 bytse of s?) Is it reverse-encoded or something like that?
It SEEMS the signature is working, it just isn’t signing with the right key because the MyCrypto took finds an address for a signature and that seems far from random.
A day wasted banging my head against a wall. Any help or random guesses would be appreciated.
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] ("Ethereum raw trx result", #Consistent(#Err(#JsonRpcError({code = -32_000; message = "sender doesn't have enough funds to send tx. The max upfront cost is: 1314506000000000000 and the sender's account only has: 0"}))))