Skip to content

Commit 76ca5be

Browse files
committed
confidential: add confidential descriptors
1 parent 5458e7c commit 76ca5be

File tree

5 files changed

+440
-0
lines changed

5 files changed

+440
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ rand = ["bitcoin/rand"]
1717

1818
[dependencies]
1919
bitcoin = "0.28.1"
20+
bitcoin_hashes = "0.11.0"
2021
elements = "0.19.0"
2122
bitcoin-miniscript = {package = "miniscript", git = "https://github.com/rust-bitcoin/rust-miniscript", rev = "44c0e4b8df30439bb22b13a0ee642a3330360613"}
2223

src/confidential/hash_to_sk.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Miniscript
2+
// Written in 2022 by
3+
// Andrew Poelstra <[email protected]>
4+
//
5+
// To the extent possible under law, the author(s) have dedicated all
6+
// copyright and related and neighboring rights to this software to
7+
// the public domain worldwide. This software is distributed without
8+
// any warranty.
9+
//
10+
// You should have received a copy of the CC0 Public Domain Dedication
11+
// along with this software.
12+
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
13+
//
14+
15+
//! Hash-To-Secret-Key blinding keys
16+
//!
17+
18+
use bitcoin_hashes::{sha256t_hash_newtype, Hash, HashEngine};
19+
use elements::encode::Encodable;
20+
use elements::secp256k1_zkp;
21+
22+
use crate::ToPublicKey;
23+
24+
/// The SHA-256 initial midstate value for the [`HashToSkHash`].
25+
const MIDSTATE_HASH_TO_SK_HASH: [u8; 32] = [
26+
0x2f, 0x85, 0x61, 0xec, 0x30, 0x88, 0xad, 0xa9, 0x5a, 0xe7, 0x43, 0xcd, 0x3c, 0x5f, 0x59, 0x7d,
27+
0xc0, 0x4b, 0xd0, 0x7f, 0x06, 0x5f, 0x1c, 0x06, 0x47, 0x89, 0x36, 0x63, 0xf3, 0x92, 0x6e, 0x65,
28+
];
29+
30+
sha256t_hash_newtype!(HashToSkHash, HashToSkTag, MIDSTATE_HASH_TO_SK_HASH, 64,
31+
doc="BIP-340 Tagged hash for deriving blinding private keys from a list of public keys", false
32+
);
33+
34+
/// Derives a blinding private key from a given script pubkey
35+
pub fn blinding_private_key<'a, Pk, I>(spk: &elements::Script, keys: I) -> secp256k1_zkp::SecretKey
36+
where
37+
Pk: ToPublicKey + 'a,
38+
I: Iterator<Item = &'a Pk>,
39+
{
40+
let mut eng = HashToSkHash::engine();
41+
spk.consensus_encode(&mut eng).expect("engines don't error");
42+
let mut sorted_keys: Vec<_> = keys.map(|k| k.to_public_key().inner.serialize()).collect();
43+
sorted_keys.sort();
44+
for key in sorted_keys {
45+
eng.input(&key);
46+
}
47+
// lol why is this conversion so hard
48+
secp256k1_zkp::SecretKey::from_slice(&HashToSkHash::from_engine(eng).into_inner()).unwrap()
49+
}
50+
51+
/// Derives a public blinding key from a given script pubkey
52+
pub fn blinding_key<'a, C, Pk, I>(
53+
secp: &secp256k1_zkp::Secp256k1<C>,
54+
spk: &elements::Script,
55+
keys: I,
56+
) -> secp256k1_zkp::PublicKey
57+
where
58+
C: secp256k1_zkp::Signing,
59+
Pk: ToPublicKey + 'a,
60+
I: Iterator<Item = &'a Pk>,
61+
{
62+
let sk = blinding_private_key(spk, keys);
63+
secp256k1_zkp::PublicKey::from_secret_key(secp, &sk)
64+
}
65+
66+
#[cfg(test)]
67+
mod tests {
68+
use bitcoin_hashes::{hex::ToHex, sha256, sha256t::Tag};
69+
use super::*;
70+
71+
#[test]
72+
fn tagged_hash() {
73+
// Check that cached midstate is computed correctly
74+
// This code taken from `tag_engine` in the rust-bitcoin tests; it is identical
75+
// to that used by the BIP-0340 hashes in Taproot
76+
let mut engine = sha256::Hash::engine();
77+
let tag_hash = sha256::Hash::hash(b"CT-Blinding-Key/1.0");
78+
engine.input(&tag_hash[..]);
79+
engine.input(&tag_hash[..]);
80+
assert_eq!(MIDSTATE_HASH_TO_SK_HASH, engine.midstate().into_inner());
81+
82+
// Test empty hash
83+
assert_eq!(
84+
HashToSkHash::from_engine(HashToSkTag::engine()).to_hex(),
85+
"d12a140aca856fbb917b931f263c42f064608985e2ce17ae5157daa17c55e8d9",
86+
);
87+
assert_eq!(
88+
HashToSkHash::hash(&[]).to_hex(),
89+
"d12a140aca856fbb917b931f263c42f064608985e2ce17ae5157daa17c55e8d9",
90+
);
91+
92+
// And hash of 100 bytes
93+
let data: Vec<u8> = (0..80).collect();
94+
assert_eq!(
95+
HashToSkHash::hash(&data).to_hex(),
96+
"e1e52419a2934d278c50e29608969d2f23c1bd1243a09bfc8026d4ed4b085e39",
97+
);
98+
}
99+
}

src/confidential/mod.rs

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
// Miniscript
2+
// Written in 2022 by
3+
// Andrew Poelstra <[email protected]>
4+
//
5+
// To the extent possible under law, the author(s) have dedicated all
6+
// copyright and related and neighboring rights to this software to
7+
// the public domain worldwide. This software is distributed without
8+
// any warranty.
9+
//
10+
// You should have received a copy of the CC0 Public Domain Dedication
11+
// along with this software.
12+
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
13+
//
14+
15+
//! Confidential Descriptors
16+
//!
17+
//! Implements ELIP ????, described at `URL`
18+
//!
19+
20+
pub mod hash_to_sk;
21+
pub mod slip77;
22+
23+
use crate::{Error, MiniscriptKey, ToPublicKey};
24+
use elements::hashes::hex;
25+
use elements::secp256k1_zkp;
26+
use crate::extensions::{CovenantExt, CovExtArgs, Extension, ParseableExt};
27+
use std::{fmt, iter};
28+
29+
/// A description of a blinding key
30+
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
31+
pub enum Key<Pk: MiniscriptKey> {
32+
/// Blinding key is computed from the hash of a sorted list of public keys
33+
///
34+
/// The first public key is included outside of the `Vec` to reduce allocations
35+
/// and to enforce that the list of keys is not empty; but it is not special in
36+
/// any way.
37+
HashToSk(Pk, Vec<Pk>),
38+
/// Blinding key is computed using SLIP77 with the given master key
39+
Slip77(slip77::MasterBlindingKey),
40+
/// Blinding key is given directly
41+
Bare(Pk),
42+
}
43+
44+
impl<Pk: MiniscriptKey + fmt::Display> fmt::Display for Key<Pk> {
45+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
46+
match *self {
47+
Key::HashToSk(ref key1, ref keys) => {
48+
write!(f, "hash_to_sk({}", key1)?;
49+
for k in keys {
50+
write!(f, ",{}", k)?;
51+
}
52+
f.write_str(")")
53+
},
54+
Key::Slip77(ref data) => {
55+
f.write_str("slip77(")?;
56+
hex::format_hex(data.as_bytes(), f)?;
57+
f.write_str(")")
58+
},
59+
Key::Bare(ref pk) => fmt::Display::fmt(pk, f),
60+
}
61+
}
62+
}
63+
64+
impl<Pk: MiniscriptKey + ToPublicKey> Key<Pk> {
65+
fn to_public_key<C: secp256k1_zkp::Signing>(
66+
&self,
67+
secp: &secp256k1_zkp::Secp256k1<C>,
68+
spk: &elements::Script,
69+
) -> secp256k1_zkp::PublicKey {
70+
match *self {
71+
Key::HashToSk(ref k1, ref keys) => hash_to_sk::blinding_key(
72+
secp,
73+
spk,
74+
iter::once(k1).chain(keys.iter()),
75+
),
76+
Key::Slip77(ref mbk) => mbk.blinding_key(secp, spk),
77+
Key::Bare(ref pk) => pk.to_public_key().inner,
78+
}
79+
}
80+
}
81+
82+
/// A confidential descriptor
83+
pub struct Descriptor<Pk: MiniscriptKey, T: Extension = CovenantExt<CovExtArgs>> {
84+
/// The blinding key
85+
pub key: Key<Pk>,
86+
/// The script descriptor
87+
pub descriptor: crate::Descriptor<Pk, T>,
88+
}
89+
90+
impl<Pk: MiniscriptKey, T: Extension> Descriptor<Pk, T> {
91+
/// Sanity checks for the underlying descriptor.
92+
pub fn sanity_check(&self) -> Result<(), Error> {
93+
self.descriptor.sanity_check()?;
94+
Ok(())
95+
}
96+
}
97+
98+
impl<Pk: MiniscriptKey + ToPublicKey, T: Extension + ParseableExt> Descriptor<Pk, T> {
99+
/// Obtains the unblinded address for this descriptor.
100+
pub fn unconfidential_address(
101+
&self,
102+
params: &'static elements::AddressParams,
103+
) -> Result<elements::Address, Error> {
104+
self.descriptor.address(params)
105+
}
106+
107+
/// Obtains the blinded address for this descriptor.
108+
pub fn address<C: secp256k1_zkp::Signing>(
109+
&self,
110+
secp: &secp256k1_zkp::Secp256k1<C>,
111+
params: &'static elements::AddressParams,
112+
) -> Result<elements::Address, Error> {
113+
let spk = self.descriptor.script_pubkey();
114+
self.descriptor
115+
.blinded_address(self.key.to_public_key(secp, &spk), params)
116+
}
117+
}
118+
119+
#[cfg(test)]
120+
mod tests {
121+
use super::*;
122+
123+
use crate::NoExt;
124+
use elements::Address;
125+
use std::str::FromStr;
126+
127+
#[test]
128+
fn bare_addr_to_confidential() {
129+
let secp = secp256k1_zkp::Secp256k1::new();
130+
131+
// taken from libwally src/test/test_confidential_addr.py
132+
let mut addr = Address::from_str("Q7qcjTLsYGoMA7TjUp97R6E6AM5VKqBik6").unwrap();
133+
let key = Key::Bare(bitcoin::PublicKey::from_str("02dce16018bbbb8e36de7b394df5b5166e9adb7498be7d881a85a09aeecf76b623").unwrap());
134+
addr.blinding_pubkey = Some(key.to_public_key(&secp, &addr.script_pubkey()));
135+
assert_eq!(addr.to_string(), "VTpz1bNuCALgavJKgbAw9Lpp9A72rJy64XPqgqfnaLpMjRcPh5UHBqyRUE4WMZ3asjqu7YEPVAnWw2EK");
136+
}
137+
138+
#[test]
139+
fn hash_to_sk_addr_to_confidential() {
140+
let secp = secp256k1_zkp::Secp256k1::new();
141+
142+
// Single key
143+
let desc: Descriptor::<secp256k1_zkp::PublicKey, NoExt> = Descriptor {
144+
key: Key::HashToSk(FromStr::from_str("02dce16018bbbb8e36de7b394df5b5166e9adb7498be7d881a85a09aeecf76b623").unwrap(), vec![]),
145+
descriptor: FromStr::from_str("elpkh(020000000000000000000000000000000000000000000000000000000000000002)").unwrap(),
146+
};
147+
148+
assert_eq!(
149+
desc.address(&secp, &elements::AddressParams::ELEMENTS).unwrap().to_string(),
150+
"CTExk1cWwWsm8PP8c9KK1nPhvYin5Ac3a7xcg9U92f76oCyRi3Zh1QcAPPavQ7UDhax5tKMTajUYyaLu",
151+
);
152+
assert_eq!(
153+
desc.unconfidential_address(&elements::AddressParams::ELEMENTS).unwrap().to_string(),
154+
"2dmYXpSu8YP6aLcJYhHfB1C19mdzSx2GPB9",
155+
);
156+
157+
// Multiple keys
158+
let keys = vec![
159+
"02fb7a8c3b2fc3e42095b51d8c28e37f15ec3da8f84ec7d51cbbc83abd39eec2d6",
160+
"03d5614e0f8983f8d1bed90299e8cb40b1f0fd61d36d7e2958682cca625d14d744",
161+
"03cb8d2ef4aaf7be6a9bb80afe798e6cf72fd102b46c44f81c31593ae62a3c7de8",
162+
"032ea1c87f4023d48ad3a30acf1ac63b6d8a876b880c0ddc9a4163ab6a25f28a2b",
163+
"03d38adcd566db859f59ba28cc6cba54e9c9ae5399b875034e6038f1e22fe3f2ff",
164+
"02729d8336d93c59ab20edb4cf7589ff5dbc159d2e239e80d0db01ddd22b994f0b",
165+
"02fde5e64596d934e0d560afff13d7f9732b3751555f836c5093ee69d4fa3d836f",
166+
"029f00e038dbc4ecd875d5170feff8f56ae7e0c0a90467454d78a4212e1964194d",
167+
"0247c0c61ad7b6671c4ea9b277bffb0b27412ae2d2795cccadb405b152a67ff250",
168+
"03e8485a732bced45ad81ac259d9889459a0c6128a16b6782cc68edfa43fea2783",
169+
"02412e2ba495c3f52f80a6db41524079f293ae2d47f6a239550879b0430b3c0e0e",
170+
"023569dac5cefeac572481ad6eb1205c59a774236352c2a34a6f04c9597ea5218f",
171+
"02bb22f63397b818ac12f43a47446333cbc3da79427469257561801f0bdd0d8342",
172+
"03fbff1f52bb55262c83a66873433e8408eec5f06a24f33eb2f2eab146e47c7d2a",
173+
"02253adb8820f73fdeec7a3472baddde79795d7792f4fb6d748b8b33bda9d30449",
174+
"03517fb63ed0136223d9b2239ce2594a78b7ad4adf3194dbfe94cd30047d2d868e",
175+
"02be9a86606e90a1cc3a97393a295ee9661e943e7585519266d230b31cf8b1812c",
176+
"034e93eb5054ab504205855ddafb4bfe1e0deacc51929c784c0b2f9b11123ec2e2",
177+
"0212d3048099d52ee4b8153f75623fc7153788f659e8c1df21ac2e6ff21561cb10",
178+
"03774eec7a3d550d18e9f89414152025b3b0ad6a342b19481f702d843cff06dfc4",
179+
];
180+
// Parse all keys
181+
let mut keys: Vec<_> = keys
182+
.into_iter()
183+
.map(|k| secp256k1_zkp::PublicKey::from_str(k).unwrap())
184+
.collect();
185+
// Take one to make a spk from
186+
let spk_key = keys.pop().unwrap();
187+
// Make a sorted copy
188+
let mut sorted_keys = keys.clone();
189+
sorted_keys.sort_by_key(|k| k.serialize());
190+
191+
// Normal segwit address
192+
let key_1 = keys.pop().unwrap();
193+
let mut desc: Descriptor::<secp256k1_zkp::PublicKey, NoExt> = Descriptor {
194+
key: Key::HashToSk(key_1, keys),
195+
descriptor: crate::Descriptor::new_wpkh(spk_key.clone()).unwrap(),
196+
};
197+
assert_eq!(
198+
desc.address(&secp, &elements::AddressParams::ELEMENTS).unwrap().to_string(),
199+
"el1qq2rr0dcmn6mfvcf7x486z3djs7j283arxspjj6adzgjsjxsa5r0v2rmqvvk2ce5ksnxcs9ecgtnryt7xg34068lggvfp79zus",
200+
);
201+
// Same address with sorted blinding keys (should be the same)
202+
let key_1 = sorted_keys.pop().unwrap();
203+
desc.key = Key::HashToSk(key_1, sorted_keys);
204+
assert_eq!(
205+
desc.address(&secp, &elements::AddressParams::ELEMENTS).unwrap().to_string(),
206+
"el1qq2rr0dcmn6mfvcf7x486z3djs7j283arxspjj6adzgjsjxsa5r0v2rmqvvk2ce5ksnxcs9ecgtnryt7xg34068lggvfp79zus",
207+
);
208+
209+
// P2PKH address
210+
desc.descriptor = crate::Descriptor::new_pkh(spk_key.clone());
211+
assert_eq!(
212+
desc.address(&secp, &elements::AddressParams::ELEMENTS).unwrap().to_string(),
213+
"CTEjvpSstrs9BBTBGP9uEmVkVxETjEPFJoX4g1z2N8RWTFm2N3y4Auw4EfBdWams5A4ryjqYpTspmkqj",
214+
);
215+
216+
// P2TR address
217+
desc.descriptor = crate::Descriptor::new_tr(spk_key.clone(), None).unwrap();
218+
assert_eq!(
219+
desc.address(&secp, &elements::AddressParams::ELEMENTS).unwrap().to_string(),
220+
"el1pqdv9pw8heqh3d329tlqyp5nfqfje4aezhp5sjafc39dxtmcwvwkfc6r5jd0rkpzukq6hd965kepcmwtxvg0fh4ak4f636gv25yky23ces7qpvfsp96eq",
221+
);
222+
}
223+
}
224+
225+
226+

0 commit comments

Comments
 (0)