Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/wally.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,12 @@ inline int ec_public_key_bip341_tweak(const PUB_KEY& pub_key, const MERKLE_ROOT&
return detail::check_ret(__FUNCTION__, ret);
}

template <class PUB_KEY, class BYTES_OUT>
inline int ec_public_key_compress(const PUB_KEY& pub_key, BYTES_OUT& bytes_out) {
int ret = ::wally_ec_public_key_compress(pub_key.data(), pub_key.size(), bytes_out.data(), bytes_out.size());
return detail::check_ret(__FUNCTION__, ret);
}

template <class PUB_KEY, class BYTES_OUT>
inline int ec_public_key_decompress(const PUB_KEY& pub_key, BYTES_OUT& bytes_out) {
int ret = ::wally_ec_public_key_decompress(pub_key.data(), pub_key.size(), bytes_out.data(), bytes_out.size());
Expand Down
20 changes: 17 additions & 3 deletions include/wally_crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -427,11 +427,25 @@ WALLY_CORE_API int wally_ec_public_key_from_private_key(
size_t len);

/**
* Create an uncompressed public key from a compressed public key.
* Create a compressed public key from a public key.
*
* :param pub_key: The public key to compress.
* :param pub_key_len: The length of ``pub_key`` in bytes. Must be `EC_PUBLIC_KEY_LEN` or `EC_PUBLIC_KEY_UNCOMPRESSED_LEN`.
* :param bytes_out: Destination for the resulting compressed public key.
* FIXED_SIZED_OUTPUT(len, bytes_out, EC_PUBLIC_KEY_LEN)
*/
WALLY_CORE_API int wally_ec_public_key_compress(
const unsigned char *pub_key,
size_t pub_key_len,
unsigned char *bytes_out,
size_t len);

/**
* Create an uncompressed public key from a public key.
*
* :param pub_key: The public key to decompress.
* :param pub_key_len: The length of ``pub_key`` in bytes. Must be `EC_PUBLIC_KEY_LEN`.
* :param bytes_out: Destination for the resulting public key.
* :param pub_key_len: The length of ``pub_key`` in bytes. Must be `EC_PUBLIC_KEY_LEN` or `EC_PUBLIC_KEY_UNCOMPRESSED_LEN`.
* :param bytes_out: Destination for the resulting uncompressed public key.
* FIXED_SIZED_OUTPUT(len, bytes_out, EC_PUBLIC_KEY_UNCOMPRESSED_LEN)
*/
WALLY_CORE_API int wally_ec_public_key_decompress(
Expand Down
47 changes: 37 additions & 10 deletions src/sign.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,25 +93,47 @@ int wally_ec_public_key_from_private_key(const unsigned char *priv_key, size_t p
return ok ? WALLY_OK : WALLY_EINVAL;
}

int wally_ec_public_key_decompress(const unsigned char *pub_key, size_t pub_key_len,
unsigned char *bytes_out, size_t len)
/* Serialize a public key of either format into the format implied by `len`.
* Keys are always parsed, so that invalid keys are rejected even when the
* input and output formats are the same.
*/
static int pk_convert_impl(const unsigned char *pub_key, size_t pub_key_len,
unsigned char *bytes_out, size_t len)
{
secp256k1_pubkey pub;
size_t len_in_out = EC_PUBLIC_KEY_UNCOMPRESSED_LEN;
unsigned int flags = len == EC_PUBLIC_KEY_LEN ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED;
size_t len_in_out = len;
bool ok;

ok = pub_key && pub_key_len == EC_PUBLIC_KEY_LEN &&
bytes_out && len == EC_PUBLIC_KEY_UNCOMPRESSED_LEN &&
pubkey_parse(&pub, pub_key, pub_key_len) &&
pubkey_serialize(bytes_out, &len_in_out, &pub, PUBKEY_UNCOMPRESSED) &&
len_in_out == EC_PUBLIC_KEY_UNCOMPRESSED_LEN;
ok = pub_key &&
(pub_key_len == EC_PUBLIC_KEY_LEN ||
pub_key_len == EC_PUBLIC_KEY_UNCOMPRESSED_LEN) &&
bytes_out && pubkey_parse(&pub, pub_key, pub_key_len) &&
pubkey_serialize(bytes_out, &len_in_out, &pub, flags) &&
len_in_out == len;

if (!ok && bytes_out)
if (!ok && bytes_out && len)
wally_clear(bytes_out, len);
wally_clear(&pub, sizeof(pub));
return ok ? WALLY_OK : WALLY_EINVAL;
}

int wally_ec_public_key_compress(const unsigned char *pub_key, size_t pub_key_len,
unsigned char *bytes_out, size_t len)
{
if (len != EC_PUBLIC_KEY_LEN)
return WALLY_EINVAL;
return pk_convert_impl(pub_key, pub_key_len, bytes_out, len);
}

int wally_ec_public_key_decompress(const unsigned char *pub_key, size_t pub_key_len,
unsigned char *bytes_out, size_t len)
{
if (len != EC_PUBLIC_KEY_UNCOMPRESSED_LEN)
return WALLY_EINVAL;
return pk_convert_impl(pub_key, pub_key_len, bytes_out, len);
}

int wally_ec_public_key_negate(const unsigned char *pub_key, size_t pub_key_len,
unsigned char *bytes_out, size_t len)
{
Expand Down Expand Up @@ -295,10 +317,15 @@ int wally_ec_sig_from_der(const unsigned char *bytes, size_t bytes_len,
const secp256k1_context *ctx = secp256k1_context_static;
bool ok;

ok = bytes && bytes_len && bytes_out && len == EC_SIGNATURE_LEN &&
ok = bytes && bytes_len && bytes_len <= EC_SIGNATURE_DER_MAX_LEN &&
bytes_out && len == EC_SIGNATURE_LEN &&
secp256k1_ecdsa_signature_parse_der(ctx, &sig_secp, bytes, bytes_len) &&
secp256k1_ecdsa_signature_serialize_compact(ctx, bytes_out, &sig_secp);

if (ok && (mem_is_zero(bytes_out, EC_SIGNATURE_LEN / 2) ||
mem_is_zero(bytes_out + EC_SIGNATURE_LEN / 2, EC_SIGNATURE_LEN / 2)))
ok = false; /* R or S are 0: this signature is invalid */

if (!ok && bytes_out)
wally_clear(bytes_out, len);
wally_clear(&sig_secp, sizeof(sig_secp));
Expand Down
1 change: 1 addition & 0 deletions src/swig_java/swig.i
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) {
%returns_array_(wally_ec_private_key_bip341_tweak, 6, 7, EC_PRIVATE_KEY_LEN);
%returns_void__(wally_ec_private_key_verify);
%returns_array_(wally_ec_public_key_bip341_tweak, 6, 7, EC_PUBLIC_KEY_LEN);
%returns_array_(wally_ec_public_key_compress, 3, 4, EC_PUBLIC_KEY_LEN);
%returns_array_(wally_ec_public_key_decompress, 3, 4, EC_PUBLIC_KEY_UNCOMPRESSED_LEN);
%returns_array_(wally_ec_public_key_from_private_key, 3, 4, EC_PUBLIC_KEY_LEN);
%returns_array_(wally_ec_public_key_negate, 3, 4, EC_PUBLIC_KEY_LEN);
Expand Down
1 change: 1 addition & 0 deletions src/swig_python/python_extra.py_in
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ descriptor_get_key_origin_fingerprint = _wrap_bin(descriptor_get_key_origin_fing
descriptor_to_script = _wrap_bin(descriptor_to_script, descriptor_to_script_get_maximum_length, resize=True)
ec_private_key_bip341_tweak = _wrap_bin(ec_private_key_bip341_tweak, EC_PRIVATE_KEY_LEN)
ec_public_key_bip341_tweak = _wrap_bin(ec_public_key_bip341_tweak, EC_PUBLIC_KEY_LEN)
ec_public_key_compress = _wrap_bin(ec_public_key_compress, EC_PUBLIC_KEY_LEN)
ec_public_key_decompress = _wrap_bin(ec_public_key_decompress, EC_PUBLIC_KEY_UNCOMPRESSED_LEN)
ec_public_key_from_private_key = _wrap_bin(ec_public_key_from_private_key, EC_PUBLIC_KEY_LEN)
ec_public_key_negate = _wrap_bin(ec_public_key_negate, EC_PUBLIC_KEY_LEN)
Expand Down
47 changes: 43 additions & 4 deletions src/test/test_sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ def sign(self, priv_key, msg, flags, out_buf, out_len=None, aux=None, aux_len=No
def test_sign_and_verify(self):
sig, sig2, sig_low_r = self.cbufferize(['00' * EC_SIGNATURE_LEN] * 3)
der, der_len = make_cbuffer('00' * EC_SIGNATURE_DER_MAX_LEN)
pub_key, pub_key2 = make_cbuffer('00' * 33)[0], make_cbuffer('00' * 33)[0]
pub_unc, pub_unc2 = make_cbuffer('00' * 65)[0], make_cbuffer('00' * 65)[0]

for case in self.get_sign_cases():
priv_key, msg, nonce, r, s = case
Expand Down Expand Up @@ -74,7 +76,6 @@ def test_sign_and_verify(self):
self.assertEqual((ret, h(sig)), (WALLY_OK, h(sig2))) # All sigs low-s

# Verify
pub_key, _ = make_cbuffer('00' * 33)
ret = wally_ec_public_key_from_private_key(priv_key, len(priv_key),
pub_key, len(pub_key))
self.assertEqual(ret, WALLY_OK)
Expand All @@ -83,15 +84,53 @@ def test_sign_and_verify(self):
FLAG_ECDSA, s, len(s))
self.assertEqual(ret, WALLY_OK)

# Validate public key
pub_unc, _ = make_cbuffer('00' * 65)
# Validate public key, Check pubkey compression/decompression
self.assertEqual(wally_ec_public_key_verify(pub_key, len(pub_key)), WALLY_OK)

ret = wally_ec_public_key_decompress(pub_key, len(pub_key), pub_unc, len(pub_unc))
self.assertEqual(ret, WALLY_OK)
self.assertEqual(wally_ec_public_key_verify(pub_key, len(pub_key)), WALLY_OK)
self.assertEqual(wally_ec_public_key_verify(pub_unc, len(pub_unc)), WALLY_OK)
ret = wally_ec_public_key_decompress(pub_unc, len(pub_unc), pub_unc2, len(pub_unc2))
self.assertEqual(ret, WALLY_OK)
self.assertEqual(pub_unc, pub_unc2)

for p, l in [(pub_key, len(pub_key)),(pub_unc, len(pub_unc))]:
ret = wally_ec_public_key_compress(p, l, pub_key2, len(pub_key2))
self.assertEqual(ret, WALLY_OK)
self.assertEqual(pub_key, pub_key2)
ret = wally_ec_public_key_verify(pub_key2, len(pub_key2))
self.assertEqual(ret, WALLY_OK)

# Invalid keys are rejected, including when no conversion is needed
bad_key, bad_key_len = make_cbuffer('02' + 'ff' * 32)
bad_unc, bad_unc_len = make_cbuffer('04' + 'ff' * 64)
for p, l in [(bad_key, bad_key_len), (bad_unc, bad_unc_len)]:
ret = wally_ec_public_key_compress(p, l, pub_key2, len(pub_key2))
self.assertEqual(ret, WALLY_EINVAL)
ret = wally_ec_public_key_decompress(p, l, pub_unc2, len(pub_unc2))
self.assertEqual(ret, WALLY_EINVAL)

# Invalid lengths are rejected
for fn, out in [(wally_ec_public_key_compress, pub_key2),
(wally_ec_public_key_decompress, pub_unc2)]:
self.assertEqual(fn(None, 33, out, len(out)), WALLY_EINVAL)
self.assertEqual(fn(pub_key, 32, out, len(out)), WALLY_EINVAL)
self.assertEqual(fn(pub_key, len(pub_key), None, len(out)), WALLY_EINVAL)
self.assertEqual(fn(pub_key, len(pub_key), out, len(out) - 1), WALLY_EINVAL)

set_fake_ec_nonce(None)

def test_der_convert(self):
out_buf, out_len = make_cbuffer('00' * EC_SIGNATURE_LEN)
long_der = '3081c4026001aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa026001bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
for bad_der in [
long_der, # R or S overflow
'3006020100020101', # R = 0
'3006020101020100' # S = 0
]:
der, der_len = make_cbuffer(bad_der)
ret = wally_ec_sig_from_der(der, der_len, out_buf, out_len)
self.assertEqual(ret, WALLY_EINVAL)

def test_invalid_inputs(self):
out_buf, out_len = make_cbuffer('00' * EC_SIGNATURE_LEN)
Expand Down
1 change: 1 addition & 0 deletions src/test/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ class wally_psbt(Structure):
('wally_ec_private_key_bip341_tweak', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_void_p, c_size_t]),
('wally_ec_private_key_verify', c_int, [c_void_p, c_size_t]),
('wally_ec_public_key_bip341_tweak', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_void_p, c_size_t]),
('wally_ec_public_key_compress', c_int, [c_void_p, c_size_t, c_void_p, c_size_t]),
('wally_ec_public_key_decompress', c_int, [c_void_p, c_size_t, c_void_p, c_size_t]),
('wally_ec_public_key_from_private_key', c_int, [c_void_p, c_size_t, c_void_p, c_size_t]),
('wally_ec_public_key_negate', c_int, [c_void_p, c_size_t, c_void_p, c_size_t]),
Expand Down
1 change: 1 addition & 0 deletions src/wasm_package/src/functions.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/wasm_package/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export function descriptor_to_script_get_maximum_length(descriptor: Ref_wally_de
export function ec_private_key_bip341_tweak(priv_key: Buffer|Uint8Array|null, merkle_root: Buffer|Uint8Array|null, flags: number): Buffer;
export function ec_private_key_verify(priv_key: Buffer|Uint8Array|null): void;
export function ec_public_key_bip341_tweak(pub_key: Buffer|Uint8Array|null, merkle_root: Buffer|Uint8Array|null, flags: number): Buffer;
export function ec_public_key_compress(pub_key: Buffer|Uint8Array|null): Buffer;
export function ec_public_key_decompress(pub_key: Buffer|Uint8Array|null): Buffer;
export function ec_public_key_from_private_key(priv_key: Buffer|Uint8Array|null): Buffer;
export function ec_public_key_negate(pub_key: Buffer|Uint8Array|null): Buffer;
Expand Down
1 change: 1 addition & 0 deletions tools/wasm_exports.sh
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ EXPORTED_FUNCTIONS="['_malloc','_free','_bip32_key_free' \
,'_wally_ec_private_key_bip341_tweak' \
,'_wally_ec_private_key_verify' \
,'_wally_ec_public_key_bip341_tweak' \
,'_wally_ec_public_key_compress' \
,'_wally_ec_public_key_decompress' \
,'_wally_ec_public_key_from_private_key' \
,'_wally_ec_public_key_negate' \
Expand Down
Loading