-
Notifications
You must be signed in to change notification settings - Fork 15
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
[SOLVED]: Verifying NFC chip signature #4
Comments
Hey there! Apologies for the delay in the answer, I'm sure you have been waiting for it but I was pretty busy with stuff. Here's the test that validates the signature you provide: (explanation below) it("Verifies chip signature", async () => {
// Defining constants
const eth_address = "1aaBF638eC3c4A5C2D5cD14fd460Fee2c364c579";
const actual_message = Buffer.concat([
Buffer.from("\x19Ethereum Signed Message:\n3"),
Buffer.from([0x01, 0x02, 0x03]),
]);
const signature = Uint8Array.from(
Buffer.from(
"93137bc7bfeaa86e26c6a9bbd6fb8acdf73ed5fd232cc2be1a0714f583f04d2e" +
"7f5d7c2461daf8649587c3c510fce05a74146cbe79341427065d0d878d154a1b",
"hex"
)
);
const recoveryId = 27 - 27;
// Creating transaction with 2 instructions
let tx = new anchor.web3.Transaction()
.add(
// Secp256k1 instruction
anchor.web3.Secp256k1Program.createInstructionWithEthAddress({
ethAddress: eth_address,
message: actual_message,
signature,
recoveryId,
})
)
.add(
// Our instruction
program.instruction.verifySecp(
ethers.utils.arrayify("0x" + eth_address),
Buffer.from(actual_message),
Buffer.from(signature),
recoveryId,
{
accounts: {
sender: person.publicKey,
ixSysvar: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY,
},
signers: [person],
}
)
);
// Sending and exploding if it fails (it won't fail)
try {
await anchor.web3.sendAndConfirmTransaction(
program.provider.connection,
tx,
[person]
);
// If all goes well, we're good!
} catch (error) {
assert.fail(
`Should not have failed with the following error:\n${error.msg}`
);
}
}); Why like this?The key part is how the parameters need to be formatted and/or treated, basically. You'll see I defined them like this: const eth_address = "1aaBF638eC3c4A5C2D5cD14fd460Fee2c364c579";
const actual_message = Buffer.concat([
Buffer.from("\x19Ethereum Signed Message:\n3"),
ethers.utils.arrayify(Buffer.from([0x01, 0x02, 0x03])),
]);
const signature = Uint8Array.from(
Buffer.from(
"93137bc7bfeaa86e26c6a9bbd6fb8acdf73ed5fd232cc2be1a0714f583f04d2e" +
"7f5d7c2461daf8649587c3c510fce05a74146cbe79341427065d0d878d154a1b",
"hex"
)
);
const recoveryId = 27 - 27; And then did some more processing on them on the instruction calls. Let me go step by step: eth_addresseth_address = "1aaBF638eC3c4A5C2D5cD14fd460Fee2c364c579"; This is not the pubkey. The Ethereum address are just the last 20 bytes of the keccak256 hash of the 32-byte pubkey. More details here. Also, Ethereum addresses have a checksum that changes the capitalization of their letters so some programs also fail if the address is not checksummed. IIRC, the Secp256k1Program in Solana does (but I'm not sure so you should check). Here, the address in your example is already checksummed and has the "0x" prefix, so I just removed it as my example tests work without the prefix. actual_messageconst actual_message = Buffer.concat([
Buffer.from("\x19Ethereum Signed Message:\n3"),
Buffer.from([0x01, 0x02, 0x03]),
]); Your example says the message is just the hexstring Ethereum signs messages by first doing some very bizarre stuff on them, namely adding the prefix NOTE: The actual original message is usually hashed before too, but here it isn't, and I am not really sure why. I leave the details of that for you to investigate since it is all chip-related implementation. For this reason you see me concatenating the buffer as-is ( All these details I'm not too familiar with, to be honest. Most of the time including this time I just play around a bit until I figure out a way to make it work. signatureconst signature = Uint8Array.from(
Buffer.from(
"93137bc7bfeaa86e26c6a9bbd6fb8acdf73ed5fd232cc2be1a0714f583f04d2e" +
"7f5d7c2461daf8649587c3c510fce05a74146cbe79341427065d0d878d154a1b",
"hex"
)
); The elliptic curve signature has three components: r, s, v. What the Solana Secp256k1 program takes as "signature" and is rather commonplace is just the components r+s concatenated together. This is why it expects a 64-byte buffer as both the r and s are 32 bytes long. recoveryIdconst recoveryId = 27 - 27; This part is so annoying it took me ages to figure out when I originally wrote this program and played around with Ethereum signatures. Now, custom implementations shift this value by some constant for reasons that I simply don't know. For this case the shift turns out to be 27. I think this varies from chain to chain but again I don't know. So, in order for the Secp256k1 program to actually be able to verify the signature you need to remove the constant from this term in order to have it in the original format. If you can understand the details or actually need them (I didn't so I didn't bother too much either) you can find the paper here. The info about this thing, funnily enough, can be found on page 27. So that's itThat being said you now know how to use the parameters in your chip and hopefully can decode signatures yourself. Keep in mind the program I implemented here just adds an extra layer with custom on-chain logic when these types of signature validations are needed so it's mostly just checking that the previous Secp256k1 Program instruction was built properly and the way you would expect. If you have any questions feel free to ask me, but do know that I might take a bit to reply generally. |
Thank you for detailed explanation; this was incredibly helpful. I appreciate the time you took to clear things up for me! |
I am attempting to verify a signature from an NFC chip using this program. Here is an example of the signature it emits:
Thank you in advance for the help!
The text was updated successfully, but these errors were encountered: