Skip to content

Commit ecabd17

Browse files
committed
descriptor: expose elip-150 blinding key tweaking
1 parent 823f21c commit ecabd17

File tree

12 files changed

+234
-28
lines changed

12 files changed

+234
-28
lines changed

include/wally.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2438,6 +2438,24 @@ inline int elements_pegout_script_size(size_t genesis_blockhash_len, size_t main
24382438
return detail::check_ret(__FUNCTION__, ret);
24392439
}
24402440

2441+
template <class BYTES, class SCRIPT, class BYTES_OUT>
2442+
inline int elip150_private_key_to_ec_private_key(const BYTES& bytes, const SCRIPT& script, BYTES_OUT& bytes_out) {
2443+
int ret = ::wally_elip150_private_key_to_ec_private_key(bytes.data(), bytes.size(), script.data(), script.size(), bytes_out.data(), bytes_out.size());
2444+
return detail::check_ret(__FUNCTION__, ret);
2445+
}
2446+
2447+
template <class BYTES, class SCRIPT, class BYTES_OUT>
2448+
inline int elip150_private_key_to_ec_public_key(const BYTES& bytes, const SCRIPT& script, BYTES_OUT& bytes_out) {
2449+
int ret = ::wally_elip150_private_key_to_ec_public_key(bytes.data(), bytes.size(), script.data(), script.size(), bytes_out.data(), bytes_out.size());
2450+
return detail::check_ret(__FUNCTION__, ret);
2451+
}
2452+
2453+
template <class BYTES, class SCRIPT, class BYTES_OUT>
2454+
inline int elip150_public_key_to_ec_public_key(const BYTES& bytes, const SCRIPT& script, BYTES_OUT& bytes_out) {
2455+
int ret = ::wally_elip150_public_key_to_ec_public_key(bytes.data(), bytes.size(), script.data(), script.size(), bytes_out.data(), bytes_out.size());
2456+
return detail::check_ret(__FUNCTION__, ret);
2457+
}
2458+
24412459
template <class NONCE, class VBF, class COMMITMENT, class GENERATOR, class BYTES_OUT>
24422460
inline int explicit_rangeproof(uint64_t value, const NONCE& nonce, const VBF& vbf, const COMMITMENT& commitment, const GENERATOR& generator, BYTES_OUT& bytes_out, size_t* written) {
24432461
int ret = ::wally_explicit_rangeproof(value, nonce.data(), nonce.size(), vbf.data(), vbf.size(), commitment.data(), commitment.size(), generator.data(), generator.size(), bytes_out.data(), bytes_out.size(), written);

include/wally_elements.h

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ WALLY_CORE_API int wally_asset_blinding_key_from_seed(
495495
size_t len);
496496

497497
/**
498-
* Generate a blinding private key for a scriptPubkey.
498+
* Generate a blinding private key for a scriptPubkey from a SLIP-0077 master blinding key.
499499
*
500500
* :param bytes: A full master blinding key, e.g. from `wally_asset_blinding_key_from_seed`,
501501
*| or a partial key of length `SHA256_LEN`, typically from the last half of the full key.
@@ -514,7 +514,7 @@ WALLY_CORE_API int wally_asset_blinding_key_to_ec_private_key(
514514
size_t len);
515515

516516
/**
517-
* Generate a blinding public key for a scriptPubkey.
517+
* Generate a blinding public key for a scriptPubkey from a SLIP-0077 master blinding key.
518518
*
519519
* :param bytes: A full master blinding key, e.g. from `wally_asset_blinding_key_from_seed`,
520520
*| or a partial key of length `SHA256_LEN`, typically from the last half of the full key.
@@ -532,6 +532,51 @@ WALLY_CORE_API int wally_asset_blinding_key_to_ec_public_key(
532532
unsigned char *bytes_out,
533533
size_t len);
534534

535+
/**
536+
* Generate a blinding private key for a scriptPubkey from an ELIP-0150 blinding private key.
537+
*
538+
* :param bytes: An ELIP-0150 blinding private key ("View Key"), e.g. from a ct() descriptor.
539+
* :param bytes_len: Length of ``bytes``. Must be `EC_PRIVATE_KEY_LEN`.
540+
* :param script: The scriptPubkey for the confidential output address.
541+
* :param script_len: Length of ``script``.
542+
* :param bytes_out: Destination for the resulting blinding private key.
543+
* FIXED_SIZED_OUTPUT(len, bytes_out, EC_PRIVATE_KEY_LEN)
544+
*/
545+
WALLY_CORE_API int wally_elip150_private_key_to_ec_private_key(
546+
const unsigned char *bytes, size_t bytes_len,
547+
const unsigned char *script, size_t script_len,
548+
unsigned char *bytes_out, size_t len);
549+
550+
/**
551+
* Generate a blinding public key for a scriptPubkey from an ELIP-0150 blinding private key.
552+
*
553+
* :param bytes: An ELIP-0150 blinding private key ("View Key"), e.g. from a ct() descriptor.
554+
* :param bytes_len: Length of ``bytes``. Must be `EC_PRIVATE_KEY_LEN`.
555+
* :param script: The scriptPubkey for the confidential output address.
556+
* :param script_len: Length of ``script``.
557+
* :param bytes_out: Destination for the resulting blinding public key.
558+
* FIXED_SIZED_OUTPUT(len, bytes_out, EC_PUBLIC_KEY_LEN)
559+
*/
560+
WALLY_CORE_API int wally_elip150_private_key_to_ec_public_key(
561+
const unsigned char *bytes, size_t bytes_len,
562+
const unsigned char *script, size_t script_len,
563+
unsigned char *bytes_out, size_t len);
564+
565+
/**
566+
* Generate a blinding public key for a scriptPubkey from an ELIP-0150 blinding public key.
567+
*
568+
* :param bytes: An ELIP-0150 blinding public key, e.g. from a ct() descriptor.
569+
* :param bytes_len: Length of ``bytes``. Must be `EC_PUBLIC_KEY_LEN`.
570+
* :param script: The scriptPubkey for the confidential output address.
571+
* :param script_len: Length of ``script``.
572+
* :param bytes_out: Destination for the resulting blinding public key.
573+
* FIXED_SIZED_OUTPUT(len, bytes_out, EC_PUBLIC_KEY_LEN)
574+
*/
575+
WALLY_CORE_API int wally_elip150_public_key_to_ec_public_key(
576+
const unsigned char *bytes, size_t bytes_len,
577+
const unsigned char *script, size_t script_len,
578+
unsigned char *bytes_out, size_t len);
579+
535580
#define WALLY_ABF_VBF_LEN 64
536581

537582
/**

src/descriptor.c

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
#include <include/wally_script.h>
1212
#ifdef BUILD_ELEMENTS
1313
#include <include/wally_elements.h>
14-
#include "tx_io.h"
1514
#endif
1615

1716
#include <limits.h>
@@ -119,14 +118,6 @@
119118
#define KIND_MINISCRIPT_OR_D (0x07000000 | KIND_MINISCRIPT)
120119
#define KIND_MINISCRIPT_OR_I (0x08000000 | KIND_MINISCRIPT)
121120

122-
#ifdef BUILD_ELEMENTS
123-
/* SHA256(CT-Blinding-Key/1.0) */
124-
static const unsigned char CT_BLINDING_KEY_1_0_SHA256[SHA256_LEN] = {
125-
0x02, 0xe0, 0xc2, 0x24, 0x76, 0xf8, 0xc5, 0xfd, 0xb7, 0x30, 0x5d, 0x9f, 0xd0, 0xe0, 0xa3, 0x56,
126-
0xb5, 0x88, 0x77, 0x69, 0x24, 0x8e, 0x04, 0xc8, 0x6f, 0xda, 0xad, 0x35, 0x11, 0x37, 0x85, 0xb4
127-
};
128-
#endif
129-
130121
struct addr_ver_t {
131122
const unsigned char network;
132123
const unsigned char version_p2pkh;
@@ -3087,21 +3078,10 @@ static int descriptor_get_addr(struct wally_descriptor *descriptor,
30873078
pubkey, sizeof(pubkey), &written);
30883079
if (ret == WALLY_OK && written != sizeof(pubkey))
30893080
ret = WALLY_ERROR; /* Unsupported pubkey - should not happen! */
3090-
if (ret == WALLY_OK) {
3091-
/* Kblind = K + Htag(K, scriptPubKey)G */
3092-
struct sha256 sha;
3093-
struct sha256_ctx ctx;
3094-
unsigned char tweaked[EC_PUBLIC_KEY_LEN];
3095-
tagged_hash_init(&ctx, CT_BLINDING_KEY_1_0_SHA256, SHA256_LEN);
3096-
sha256_update(&ctx, pubkey, sizeof(pubkey));
3097-
hash_varbuff(&ctx, script, script_len); /* Consensus encoding */
3098-
sha256_done(&ctx, &sha);
3099-
ret = wally_ec_public_key_tweak(pubkey, sizeof(pubkey),
3100-
sha.u.u8, sizeof(sha),
3101-
tweaked, sizeof(tweaked));
3102-
memcpy(pubkey, tweaked, sizeof(tweaked));
3103-
wally_clear(tweaked, sizeof(tweaked));
3104-
}
3081+
if (ret == WALLY_OK)
3082+
ret = wally_elip150_public_key_to_ec_public_key(pubkey, sizeof(pubkey),
3083+
script, script_len,
3084+
pubkey, sizeof(pubkey));
31053085
} else
31063086
ret = WALLY_ERROR; /* FIXME: Support ELIP 151 */
31073087

src/elements.c

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "internal.h"
22
#ifdef BUILD_ELEMENTS
33
#include "script_int.h"
4+
#include "tx_io.h"
45
#include <include/wally_address.h>
56
#include <include/wally_bip32.h>
67
#include <include/wally_elements.h>
@@ -13,10 +14,16 @@
1314
#include <secp256k1_whitelist.h>
1415

1516

16-
static const unsigned char LABEL_STR[] = {
17+
static const unsigned char SLIP77_LABEL[] = {
1718
'S', 'L', 'I', 'P', '-', '0', '0', '7', '7'
1819
};
1920

21+
/* SHA256(CT-Blinding-Key/1.0) */
22+
static const unsigned char CT_BLINDING_KEY_1_0_SHA256[SHA256_LEN] = {
23+
0x02, 0xe0, 0xc2, 0x24, 0x76, 0xf8, 0xc5, 0xfd, 0xb7, 0x30, 0x5d, 0x9f, 0xd0, 0xe0, 0xa3, 0x56,
24+
0xb5, 0x88, 0x77, 0x69, 0x24, 0x8e, 0x04, 0xc8, 0x6f, 0xda, 0xad, 0x35, 0x11, 0x37, 0x85, 0xb4
25+
};
26+
2027
static int parse_generator(const secp256k1_context *ctx,
2128
const unsigned char *generator, size_t generator_len,
2229
secp256k1_generator *dest)
@@ -808,7 +815,8 @@ int wally_asset_blinding_key_from_seed(
808815

809816
ret = wally_symmetric_key_from_seed(bytes, bytes_len, root, sizeof(root));
810817
if (ret == WALLY_OK) {
811-
ret = wally_symmetric_key_from_parent(root, sizeof(root), 0, LABEL_STR, sizeof(LABEL_STR),
818+
ret = wally_symmetric_key_from_parent(root, sizeof(root), 0,
819+
SLIP77_LABEL, sizeof(SLIP77_LABEL),
812820
bytes_out, len);
813821
wally_clear(root, sizeof(root));
814822
}
@@ -874,6 +882,93 @@ int wally_asset_blinding_key_to_ec_public_key(
874882
#endif /* BUILD_ELEMENTS */
875883
}
876884

885+
#ifdef BUILD_ELEMENTS
886+
static void elip150_tagged_hash(const unsigned char *pubkey, size_t pubkey_len,
887+
const unsigned char *script, size_t script_len,
888+
struct sha256 *sha_out)
889+
{
890+
struct sha256_ctx ctx;
891+
tagged_hash_init(&ctx, CT_BLINDING_KEY_1_0_SHA256, SHA256_LEN);
892+
sha256_update(&ctx, pubkey, pubkey_len);
893+
hash_varbuff(&ctx, script, script_len); /* Consensus encoding */
894+
sha256_done(&ctx, sha_out);
895+
}
896+
#endif /* BUILD_ELEMENTS */
897+
898+
int wally_elip150_private_key_to_ec_private_key(
899+
const unsigned char *bytes, size_t bytes_len,
900+
const unsigned char *script, size_t script_len,
901+
unsigned char *bytes_out, size_t len)
902+
{
903+
#ifndef BUILD_ELEMENTS
904+
return WALLY_ERROR;
905+
#else
906+
unsigned char pubkey[EC_PUBLIC_KEY_LEN];
907+
int ret;
908+
909+
if (!bytes || bytes_len != EC_PRIVATE_KEY_LEN || !script || !script_len ||
910+
!bytes_out || len != EC_PRIVATE_KEY_LEN)
911+
return WALLY_EINVAL;
912+
913+
ret = wally_ec_public_key_from_private_key(bytes, bytes_len,
914+
pubkey, sizeof(pubkey));
915+
if (ret == WALLY_OK) {
916+
struct sha256 sha;
917+
unsigned char tweaked[EC_PRIVATE_KEY_LEN];
918+
elip150_tagged_hash(pubkey, sizeof(pubkey), script, script_len, &sha);
919+
ret = wally_ec_scalar_add(bytes, bytes_len, sha.u.u8, sizeof(sha),
920+
tweaked, sizeof(tweaked));
921+
if (ret == WALLY_OK)
922+
memcpy(bytes_out, tweaked, sizeof(tweaked));
923+
}
924+
return ret;
925+
#endif /* BUILD_ELEMENTS */
926+
}
927+
928+
int wally_elip150_private_key_to_ec_public_key(
929+
const unsigned char *bytes, size_t bytes_len,
930+
const unsigned char *script, size_t script_len,
931+
unsigned char *bytes_out, size_t len)
932+
{
933+
#ifndef BUILD_ELEMENTS
934+
return WALLY_ERROR;
935+
#else
936+
unsigned char pubkey[EC_PUBLIC_KEY_LEN];
937+
int ret = wally_ec_public_key_from_private_key(bytes, bytes_len,
938+
pubkey, sizeof(pubkey));
939+
if (ret == WALLY_OK)
940+
ret = wally_elip150_public_key_to_ec_public_key(pubkey, sizeof(pubkey),
941+
script, script_len,
942+
bytes_out, len);
943+
return ret;
944+
#endif /* BUILD_ELEMENTS */
945+
}
946+
947+
int wally_elip150_public_key_to_ec_public_key(
948+
const unsigned char *bytes, size_t bytes_len,
949+
const unsigned char *script, size_t script_len,
950+
unsigned char *bytes_out, size_t len)
951+
{
952+
#ifndef BUILD_ELEMENTS
953+
return WALLY_ERROR;
954+
#else
955+
struct sha256 sha;
956+
unsigned char tweaked[EC_PUBLIC_KEY_LEN];
957+
int ret;
958+
959+
if (!bytes || bytes_len != EC_PUBLIC_KEY_LEN || !script || !script_len ||
960+
!bytes_out || len != EC_PUBLIC_KEY_LEN)
961+
return WALLY_EINVAL;
962+
963+
elip150_tagged_hash(bytes, bytes_len, script, script_len, &sha);
964+
ret = wally_ec_public_key_tweak(bytes, bytes_len, sha.u.u8, sizeof(sha),
965+
tweaked, sizeof(tweaked));
966+
if (ret == WALLY_OK)
967+
memcpy(bytes_out, tweaked, sizeof(tweaked));
968+
return ret;
969+
#endif /* BUILD_ELEMENTS */
970+
}
971+
877972
#define BK_ABF 0x1
878973
#define BK_VBF 0x2
879974

src/swig_java/jni_elements_extra.java_in

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,21 @@
4747
return asset_blinding_key_to_ec_public_key(asset_blinding_key, scriptpubkey, null);
4848
}
4949

50+
public final static byte[] elip150_private_key_to_ec_private_key(byte[] priv_key,
51+
byte[] scriptpubkey) {
52+
return elip150_private_key_to_ec_private_key(priv_key, scriptpubkey, null);
53+
}
54+
55+
public final static byte[] elip150_private_key_to_ec_public_key(byte[] priv_key,
56+
byte[] scriptpubkey) {
57+
return elip150_private_key_to_ec_public_key(priv_key, scriptpubkey, null);
58+
}
59+
60+
public final static byte[] elip150_public_key_to_ec_public_key(byte[] pub_key,
61+
byte[] scriptpubkey) {
62+
return elip150_public_key_to_ec_public_key(pub_key, scriptpubkey, null);
63+
}
64+
5065
public final static long asset_unblind(byte[] pub_key, byte[] priv_key, byte[] proof,
5166
byte[] commitment, byte[] extra_in,
5267
byte[] generator,

src/swig_java/swig.i

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,9 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) {
617617
%returns_void__(wally_ec_xonly_public_key_verify);
618618
%returns_array_(wally_ecdh, 5, 6, SHA256_LEN);
619619
%returns_array_(wally_ecdh_nonce_hash, 5, 6, SHA256_LEN);
620+
%returns_array_(wally_elip150_private_key_to_ec_private_key, 5, 6, EC_PUBLIC_KEY_LEN);
621+
%returns_array_(wally_elip150_private_key_to_ec_public_key, 5, 6, EC_PUBLIC_KEY_LEN);
622+
%returns_array_(wally_elip150_public_key_to_ec_public_key, 5, 6, EC_PUBLIC_KEY_LEN);
620623
%returns_size_t(wally_explicit_rangeproof);
621624
%returns_void__(wally_explicit_rangeproof_verify);
622625
%returns_array_(wally_explicit_surjectionproof, 7, 8, ASSET_EXPLICIT_SURJECTIONPROOF_LEN);

src/swig_python/python_extra.py_in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,9 @@ if is_elements_build():
285285
ecdh_nonce_hash = _wrap_bin(ecdh_nonce_hash, SHA256_LEN)
286286
elements_pegin_contract_script_from_bytes = _wrap_bin(elements_pegin_contract_script_from_bytes, elements_pegin_contract_script_from_bytes_len, resize=True)
287287
elements_pegout_script_from_bytes = _wrap_bin(elements_pegout_script_from_bytes, elements_pegout_script_from_bytes_len, resize=True)
288+
elip150_private_key_to_ec_private_key = _wrap_bin(elip150_private_key_to_ec_private_key, EC_PRIVATE_KEY_LEN)
289+
elip150_private_key_to_ec_public_key = _wrap_bin(elip150_private_key_to_ec_public_key, EC_PUBLIC_KEY_LEN)
290+
elip150_public_key_to_ec_public_key = _wrap_bin(elip150_public_key_to_ec_public_key, EC_PUBLIC_KEY_LEN)
288291
explicit_rangeproof = _wrap_bin(explicit_rangeproof, ASSET_EXPLICIT_RANGEPROOF_MAX_LEN, resize=True)
289292
explicit_surjectionproof = _wrap_bin(explicit_surjectionproof, ASSET_EXPLICIT_SURJECTIONPROOF_LEN)
290293
psbt_blind = psbt_blind_alloc

src/test/test_elements.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,41 @@ def test_deterministic_blinding_factors(self):
256256
continue # Skip final VBF
257257
self.assertEqual(h(out[:o_len]), utf8(expected))
258258

259+
def test_elip150_blinding_keys(self):
260+
if not wally_is_elements_build()[1]:
261+
self.skipTest('Elements support not enabled')
262+
263+
# Ensure tweaking private and public keys results in the same key
264+
priv_key, priv_key_len = make_cbuffer('66'*32)
265+
pub_key, pub_key_len = make_cbuffer('00'*33)
266+
ret = wally_ec_public_key_from_private_key(priv_key, priv_key_len,
267+
pub_key, pub_key_len)
268+
self.assertEqual(WALLY_OK, ret)
269+
script, script_len = make_cbuffer('11'*40)
270+
out_priv, out_priv_len = make_cbuffer('00'*32)
271+
out_pub, out_pub_len = make_cbuffer('00'*33)
272+
ret = wally_elip150_private_key_to_ec_private_key(priv_key, priv_key_len,
273+
script, script_len,
274+
out_priv, out_priv_len)
275+
self.assertEqual(WALLY_OK, ret)
276+
ret = wally_ec_public_key_from_private_key(out_priv, out_priv_len,
277+
out_pub, out_pub_len)
278+
self.assertEqual(WALLY_OK, ret)
279+
expected_pubkey = h(out_pub)
280+
281+
ret = wally_elip150_private_key_to_ec_public_key(priv_key, priv_key_len,
282+
script, script_len,
283+
out_pub, out_pub_len)
284+
self.assertEqual(WALLY_OK, ret)
285+
self.assertEqual(expected_pubkey, h(out_pub))
286+
287+
ret = wally_elip150_public_key_to_ec_public_key(pub_key, pub_key_len,
288+
script, script_len,
289+
out_pub, out_pub_len)
290+
self.assertEqual(WALLY_OK, ret)
291+
self.assertEqual(expected_pubkey, h(out_pub))
292+
293+
259294
def test_elements_tx_weights(self):
260295
if not wally_is_elements_build()[1]:
261296
self.skipTest('Elements support not enabled')

src/test/util.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,9 @@ class wally_psbt(Structure):
371371
('wally_elements_pegin_contract_script_from_bytes', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_void_p, c_size_t, c_size_t_p]),
372372
('wally_elements_pegout_script_from_bytes', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_void_p, c_size_t, c_size_t_p]),
373373
('wally_elements_pegout_script_size', c_int, [c_size_t, c_size_t, c_size_t, c_size_t, c_size_t_p]),
374+
('wally_elip150_private_key_to_ec_private_key', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t]),
375+
('wally_elip150_private_key_to_ec_public_key', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t]),
376+
('wally_elip150_public_key_to_ec_public_key', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t]),
374377
('wally_explicit_rangeproof', c_int, [c_uint64, c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_size_t_p]),
375378
('wally_explicit_rangeproof_verify', c_int, [c_void_p, c_size_t, c_uint64, c_void_p, c_size_t, c_void_p, c_size_t]),
376379
('wally_explicit_surjectionproof', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t]),

src/wasm_package/src/functions.js

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)