Skip to content
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

Add secp256k1 verify and pubKey recovery function #583

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

heliuchuan
Copy link
Contributor

@heliuchuan heliuchuan commented Nov 18, 2024

Description

With secp256k1 you can recover the publicKey from the signature and message. This is common in EVM world. This will ease migrations as it allows users to use familiar APIs, which may be missing the publicKey.

Test Plan

e2e test added

Related Links

Checklist

  • Have you ran pnpm fmt?
  • Have you updated the CHANGELOG.md?

@heliuchuan heliuchuan marked this pull request as ready for review November 18, 2024 15:11
@heliuchuan heliuchuan requested a review from a team as a code owner November 18, 2024 15:11
*/
async verifySecp256k1Account(args: {
message: HexInput;
signature: HexInput | Secp256k1Signature | AnySignature;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we accept AnySignature if we only verify for Secp256k1Account? why is Secp256k1Signature is not part of AnySignature? Also, why do we accept HexInput as a signature? dont we want to make sure the function accepts a valid signature?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, public key recovery should be in the signature to return. I don't think it belongs at the account level given you can't recover the private key

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because Secp256k1Account does not exist. SingleKeyAccount exists which returns AnySignature when it signs something. You can get the inner Secp256k1Signature via signature.signature, but you would still have to type check it into Secp256k1Signature. Having the function handle it seems better DevX

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gregnazario not sure what you are suggesting

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh and HexInput is again for convenience. The function will check signature validity.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also would expect this function to be in a different place than Account.

I think Secp256k1PublicKey.fromMessageAndSignature makes total sense (fromSignedMessage could be even more concise).

Ideally that's all the devs would need, and they could do the following:

const innerPublicKey = new Secp256k1PublicKey.fromSignedMessage({ message, signature });
const publicKey = new AnyPublicKey({ publicKey: innerPublicKey });
const derivedAddress = publicKey.authKey().derivedAddress.toString();

// Simple case
const isValid = derivedAddress === accountAddress;

// Handle rotated auth keys
const { authentication_key } = await aptos.getAccountInfo({ accountAddress });
const isValid = derivedAddress === authentication_key;

now.. how can we make this even easier / more concise?

  1. Going from Secp256k1PublicKey to AnyPublicKey is a bit annoying, maybe we can have the constructor there instead: AnyPublicKey.fromSecp256k1SignedMessage({ message, signature })
  2. The action of verifying the publicKey is associated to an account from its address is agnostic to the signature scheme, so we could have verifyAuthenticationKey({ authKey, accountAddress }) which could live in the api to avoid dependency cycles

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really hate the AnyPublicKey name ugh

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the problem is you need to look up the auth key with the address, and only via the auth key can you figure out which public key is correct. If a recovery is provided 2 public keys would be returned.

I'm down for AnyPublicKey.fromSecp256k1SignedMessage({ message, signature }) though

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I re-evaluated it and did similar here aptos-labs/aptos-go-sdk#108

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the problem is you need to look up the auth key with the address, and only via the auth key can you figure out which public key is correct. If a recovery is provided 2 public keys would be returned.

Oh I see, gotcha makes sense

src/internal/account.ts Outdated Show resolved Hide resolved
tests/e2e/api/account.test.ts Show resolved Hide resolved
src/core/crypto/secp256k1.ts Show resolved Hide resolved
Copy link
Collaborator

@gregnazario gregnazario left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's compartmentalize this properly to the Secp256k1 signatures since it applies to that, and then we won't need to do other parsing

gregnazario
gregnazario previously approved these changes Nov 21, 2024
@gregnazario gregnazario dismissed their stale review November 21, 2024 17:05

Breaking change not marked

src/core/crypto/secp256k1.ts Outdated Show resolved Hide resolved
src/core/crypto/singleKey.ts Outdated Show resolved Hide resolved
src/core/crypto/singleKey.ts Outdated Show resolved Hide resolved
src/core/crypto/secp256k1.ts Outdated Show resolved Hide resolved
src/core/crypto/singleKey.ts Outdated Show resolved Hide resolved
src/types/types.ts Outdated Show resolved Hide resolved
*/
async verifySecp256k1Account(args: {
message: HexInput;
signature: HexInput | Secp256k1Signature | AnySignature;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also would expect this function to be in a different place than Account.

I think Secp256k1PublicKey.fromMessageAndSignature makes total sense (fromSignedMessage could be even more concise).

Ideally that's all the devs would need, and they could do the following:

const innerPublicKey = new Secp256k1PublicKey.fromSignedMessage({ message, signature });
const publicKey = new AnyPublicKey({ publicKey: innerPublicKey });
const derivedAddress = publicKey.authKey().derivedAddress.toString();

// Simple case
const isValid = derivedAddress === accountAddress;

// Handle rotated auth keys
const { authentication_key } = await aptos.getAccountInfo({ accountAddress });
const isValid = derivedAddress === authentication_key;

now.. how can we make this even easier / more concise?

  1. Going from Secp256k1PublicKey to AnyPublicKey is a bit annoying, maybe we can have the constructor there instead: AnyPublicKey.fromSecp256k1SignedMessage({ message, signature })
  2. The action of verifying the publicKey is associated to an account from its address is agnostic to the signature scheme, so we could have verifyAuthenticationKey({ authKey, accountAddress }) which could live in the api to avoid dependency cycles

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants