Skip to content

[API CHANGE] Introduce pubkey and signature types #282

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

Merged
merged 3 commits into from
Jul 26, 2015

Conversation

sipa
Copy link
Contributor

@sipa sipa commented Jul 20, 2015

This introduces a new data type secp256k1_pubkey_t, which is an abstract parsed public key, convertible to/from serialized variable-size public keys.

This avoids all API problems resulting from needing to pass pubkey sizes and pointers to pubkey sizes anywhere public keys are needed.

Advantages:

  • No need for secp256k1_ec_pubkey_verify, secp256k1_ec_pubkey_compress, secp256k1_ec_pubkey_decompress
  • Reusing the same public key in multiple operations is faster due to avoiding decompression costs on every use.
  • Less parameters to pass around.
  • No need for separate secp256k1_ecdsa_verify return code to deal with invalid public keys.

Disadvantages:

  • Need for API calls secp256k1_ec_pubkey_serialize and secp256k1_ec_pubkey_parse.
  • Slightly slower for verification (0.4%) due to extra serialization and weak validation overhead between parsing and verifying.
  • Potentially unsafe when passing an uninitialized secp256k1_pubkey_t for verification, though this is mitigated as much as possible by wiping the output secp256k1_pubkey_t any time an invalid one would be returned, and not accepting a wiped one as input.

@sipa sipa changed the title Introduce secp256k1_pubkey_t type [API CHANGE] Introduce secp256k1_pubkey_t type Jul 20, 2015
@sipa
Copy link
Contributor Author

sipa commented Jul 20, 2015

An alternative to this is having a secp256k1_pubkey_t type that maintains the compressedness, and is just a serialized pubkey, but in a constant-size buffer.

This has as advantages that it does not add any significant performance overhead, and does not risk extra problems when uninitialized secp256k1_pubkey_t's are passed in (though that would still be a risky bug for other reasons), but it also would not avoid the compress/decompress/verify API calls, not offer any performance advantages for repeated pubkey use, and be a bit unintuitive for having parsing of an invalid pubkey potentially succeed.

@sipa
Copy link
Contributor Author

sipa commented Jul 23, 2015

@afk11 Any comments here? This significantly changes the way public keys are handled, and removes the need for the secp256k1_ec_pubkey_compress function which you added.

@sipa
Copy link
Contributor Author

sipa commented Jul 24, 2015

Added extra documentation.

secp256k1_fe_get_b32(pubkey->data + 32, &ge->y);
}

int secp256k1_ec_pubkey_parse(const secp256k1_context_t* ctx, secp256k1_pubkey_t* pubkey, const unsigned char *input, int inputlen) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The indentation in this function is different from all the others.

@apoelstra
Copy link
Contributor

It looks like bench_recover fails:

[apoelstra@titanic secp256k1]$ ./bench_recover 
src/bench_recover.c:26: test condition failed: secp256k1_ecdsa_recover_compact(data->ctx, data->msg, data->sig, &pubkey, 1)
Aborted

@sipa sipa force-pushed the pubkeytype branch 2 times, most recently from c2f173c to e71b1f4 Compare July 24, 2015 19:43
@DavidEGrayson
Copy link

I was expecting @gmaxwell to point out that _t is reserved by POSIX (see #198).

@sipa
Copy link
Contributor Author

sipa commented Jul 25, 2015

@DavidEGrayson I thought I already answered, but can't see the post now

@gmaxwell would be right to point that out, but then we should consistently change it throughout the codebase once, not introduce multiple coding standards.

@sipa
Copy link
Contributor Author

sipa commented Jul 25, 2015

I've changed the implementation of secp256k1_pubkey_load/save now, using secp256k1_ge_storage where possible... and the result is around 0.15% faster now than before the pubkey_t introduction.

@sipa sipa changed the title [API CHANGE] Introduce secp256k1_pubkey_t type [API CHANGE] Introduce pubkey and signature types Jul 25, 2015
@sipa
Copy link
Contributor Author

sipa commented Jul 25, 2015

I extended the scope of this patch, and introduced a secp256k1_ecdsa_signature_t for ECDSA signatures.

* 0: incorrect signature
* -1: invalid public key
* -2: invalid signature
* 0: incorrect or unparseable signature
* In: ctx: a secp256k1 context object, initialized for verification.
* msg32: the 32-byte message hash being verified (cannot be NULL)
* sig: the signature being verified (cannot be NULL)
* siglen: the length of the signature
Copy link
Contributor

Choose a reason for hiding this comment

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

Update comment for removal of siglen

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

/** Compute the public key for a secret key.
* In: ctx: pointer to a context object, initialized for signing (cannot be NULL)
* compressed: whether the computed public key should be compressed
* seckey: pointer to a 32-byte private key (cannot be NULL)
* Out: pubkey: pointer to a 33-byte (if compressed) or 65-byte (if uncompressed)
* Out: pubkey: pointer to the created public key (cannot be NULL)
* area to store the public key (cannot be NULL)
* pubkeylen: pointer to int that will be updated to contains the pubkey's
* length (cannot be NULL)
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove second line under pubkey, entry for pubkeylen, entry for compressed in this comment

@@ -54,11 +54,152 @@ void secp256k1_context_destroy(secp256k1_context_t* ctx) {
free(ctx);
}

int secp256k1_ecdsa_verify(const secp256k1_context_t* ctx, const unsigned char *msg32, const unsigned char *sig, int siglen, const unsigned char *pubkey, int pubkeylen) {
static void secp256k1_pubkey_load(secp256k1_ge_t* ge, const secp256k1_pubkey_t* pubkey) {
if (sizeof(secp256k1_ge_storage_t) == 64) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Under what circumstances is this not 64? You are depending on its exact format in the case that it is, so I'm confused about what you think could change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Who knows what a compiler can do with integer sizes and alignments. Maybe we want to add debug fields at some point, when VERIFY is enabled.

It's also not relying on its exact representation, only that it can be memcpy'd to and from a 64-byte array, which is faster than going through the b32 representation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In any case, this probably requires some comments in the code.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I realized that you weren't depending on the representation down below when I saw you were doing the same thing with scalar_t. I think you should add a comment saying that (assuming the data fits) the API only allows the content of pubkey->data to be a memcpy of a preexisting secp256k1_ge_storage_t, so this is fine.

@sipa
Copy link
Contributor Author

sipa commented Jul 26, 2015

Added several comments about the representation of pubkey and signature types.

@apoelstra
Copy link
Contributor

I find it a little confusing to have two signature types, especially ones that are so similarly named. It's hard to remember which one is the public one and which is the internal.

The internal one, secp256k1_ecdsa_sig_t, consists of an (r, s) pair, and is used only by the five functions defined in ecdsa.h. If you grep for these five functions you will see that every one of them occurs in only two places: in their passthroughs in the public API, and in the unit tests.

I propose:

  • Dropping secp256k1_ecdsa_sig_t and having the ecdsa.h functions take a public secp256k1_ecdsa_signature_t.
  • Moving signature_load and signature_save into ecdsa_impl.h, and having them output separate r, s scalars rather than a single struct (since the ecdsa.h functions use the individual scalars and never pass around the complete type anyway).

This would avoid having separate types and remove some double conversions (like calling secp256k1_ecdsa_sig_parse immediately followed by secp256k1_ecdsa_signature_save in secp256k1_ecdsa_signature_parse_der). Also note that recid is already carried around separately from secp256k1_ecdsa_sig_t, so this type is not doing a great job of encapsulation anyway.

I don't feel super strongly about this, it's up to you.

@sipa
Copy link
Contributor Author

sipa commented Jul 26, 2015

I think that would be a layering violation. the toplevel module defines a data type for external use, and depends on the internal code for operations on it. The lowlevel module would end up depending on the toplevel module if it needed to be passed in the toplevel data type, resulting in a weak circular dependency.

Furthermore, both datatypes have a different use case: one is obscurity and consistency (at least wrt its size), the other is efficiency.

Moving the load and save to ecdsa feels wrong, as it makes the ecdsa module depend on external API peculiarities.

One option is to drop the internal type, and just operate on scalar_t's inside ecdsa. The other is perhaps to rename the internal types to something more obvious (secp256k1_ecdsa_signature_internal_t ?), and maybe move the recid inside it.

@apoelstra
Copy link
Contributor

That's a good point.

Between the two options you listed I'd prefer to just operate on scalar_t's, since there are only two of them and they have standard names.

@afk11
Copy link
Contributor

afk11 commented Jul 26, 2015

@sipa concept ACK on this, the performance boost should affect me also since presently I have to serialize my PublicKey types whenever calling secp256k1_* functions. I'll get to testing this today.

@sipa
Copy link
Contributor Author

sipa commented Jul 26, 2015

@apoelstra Added a commit which removes the internal secp256k1_ecdsa_sig_t.

@apoelstra
Copy link
Contributor

Thanks, I'm much happier with this.

@apoelstra
Copy link
Contributor

ACK

@sipa sipa merged commit 18c329c into bitcoin-core:master Jul 26, 2015
sipa added a commit that referenced this pull request Jul 26, 2015
18c329c Remove the internal secp256k1_ecdsa_sig_t type (Pieter Wuille)
74a2acd Add a secp256k1_ecdsa_signature_t type (Pieter Wuille)
23cfa91 Introduce secp256k1_pubkey_t type (Pieter Wuille)
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