diff --git a/Changes b/Changes index 60d5c96..11d676f 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,8 @@ Revision history for Bitcoin-Secp256k1 {{$NEXT}} + [New interface] + - Added method combine_public_keys 0.002 - 2024-09-20 - Module should now be buildable on threaded perls diff --git a/Secp256k1.xs b/Secp256k1.xs index b6fc4f9..f907ad6 100644 --- a/Secp256k1.xs +++ b/Secp256k1.xs @@ -83,6 +83,31 @@ unsigned char* size_bytestr_from_sv(SV *perlval, size_t wanted_size, char *argna return bytestr; } +secp256k1_pubkey* pubkey_from_sv(secp256k1_perl *ctx, SV *data) +{ + if (!SvOK(data) || SvROK(data)) { + croak("public key must be defined and not a reference"); + } + + size_t key_size; + unsigned char *key = bytestr_from_sv(data, &key_size); + + secp256k1_pubkey *result_pubkey = malloc(sizeof *result_pubkey); + int result = secp256k1_ec_pubkey_parse( + ctx->ctx, + result_pubkey, + key, + key_size + ); + + if (!result) { + free(result_pubkey); + croak("the input does not appear to be a valid public key"); + } + + return result_pubkey; +} + void copy_bytestr(unsigned char *to, unsigned char *from, size_t size) { int i; @@ -168,28 +193,9 @@ _pubkey(self, ...) CODE: secp256k1_perl *ctx = ctx_from_sv(self); if (items > 1 && SvOK(ST(1))) { - SV *new_pubkey = ST(1); - if (SvROK(new_pubkey)) { - croak("public key must not be a reference"); - } - - size_t key_size; - unsigned char *key = bytestr_from_sv(new_pubkey, &key_size); - - secp256k1_pubkey *result_pubkey = malloc(sizeof *result_pubkey); - int result = secp256k1_ec_pubkey_parse( - ctx->ctx, - result_pubkey, - key, - key_size - ); - - if (!result) { - free(result_pubkey); - croak("the input does not appear to be a valid public key"); - } - - secp256k1_perl_replace_pubkey(ctx, result_pubkey); + SV *pubkey_data = ST(1); + secp256k1_pubkey *new_pubkey = pubkey_from_sv(ctx, pubkey_data); + secp256k1_perl_replace_pubkey(ctx, new_pubkey); } unsigned int compression = SECP256K1_EC_COMPRESSED; @@ -521,6 +527,46 @@ _pubkey_mul(self, tweak) croak("multiplication arguments are not valid"); } +# Combines public keys together +void +_pubkey_combine(self, ...) + SV *self + CODE: + secp256k1_perl *ctx = ctx_from_sv(self); + int all_keys = items - 1; + if (all_keys == 0) { + croak("need at least one public key to combine"); + } + + secp256k1_pubkey **keys = malloc(sizeof(keys) * all_keys); + int i; + + for (i = 0; i < all_keys; ++i) { + secp256k1_pubkey *new_key = pubkey_from_sv(ctx, ST(i + 1)); + keys[i] = new_key; + } + + secp256k1_pubkey *result_pubkey = malloc(sizeof *result_pubkey); + int result = secp256k1_ec_pubkey_combine( + ctx->ctx, + result_pubkey, + keys, + all_keys + ); + + for (i = 0; i < all_keys; ++i) { + free(keys[i]); + } + free(keys); + + if (!result) { + free(result_pubkey); + croak("resulting sum of pubkeys is not valid"); + } + + secp256k1_perl_replace_pubkey(ctx, result_pubkey); + + # Destructor void DESTROY(self) diff --git a/lib/Bitcoin/Secp256k1.pm b/lib/Bitcoin/Secp256k1.pm index 7378377..a2be607 100644 --- a/lib/Bitcoin/Secp256k1.pm +++ b/lib/Bitcoin/Secp256k1.pm @@ -164,6 +164,15 @@ sub multiply_private_key return $self->_privkey_mul($private_key, $tweak); } +sub combine_public_keys +{ + my ($self, @public_keys) = @_; + + $self->_pubkey_combine(@public_keys); + + return $self->_pubkey; +} + 1; __END__ @@ -345,6 +354,14 @@ Same as L, but performs multiplication instead of addition. Same as L, but performs multiplication instead of addition. +=head3 combine_public_keys + + $combined = $secp256k1->combine_public_keys(@pubkeys) + +Combines C<@pubkeys> together, returning a new pubkey. + +If the arguments or the resulting key are not valid, an exception will be thrown. + =head1 IMPLEMENTATION The module consists of two layers: diff --git a/t/api.t b/t/api.t index e301f6c..c8d6228 100644 --- a/t/api.t +++ b/t/api.t @@ -80,5 +80,15 @@ subtest 'should multiply' => sub { is $secp->multiply_private_key($t{privkey}, $tweak), $multiplied_privkey, 'multiplied privkey ok'; }; +subtest 'should combine' => sub { + my @to_combine = ( + pack('H*', '0311ab47c9252066f0ca5946d70c3aaac1486d65969b90cd57207476963c9f9af3'), + pack('H*', '0260213f6d967636c54d8845c23098e0f63d906b7903d23692efa155a155eda169'), + ); + my $combined_pubkey = pack 'H*', '0255c3386d6833d5e1ad6d863afc1cf5d8ffdc0ebc78e4241e845a6c2cbd78157b'; + + is $secp->combine_public_keys($t{pubkey}, @to_combine), $combined_pubkey, 'combined pubkey ok'; +}; + done_testing;