Skip to content

Commit 358f663

Browse files
committed
Add ELIP-deterministic-descriptor-blinding-key module
1 parent af410f6 commit 358f663

File tree

2 files changed

+187
-0
lines changed

2 files changed

+187
-0
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// Miniscript
2+
// Written in 2023 by Leonardo Comandini
3+
//
4+
// To the extent possible under law, the author(s) have dedicated all
5+
// copyright and related and neighboring rights to this software to
6+
// the public domain worldwide. This software is distributed without
7+
// any warranty.
8+
//
9+
// You should have received a copy of the CC0 Public Domain Dedication
10+
// along with this software.
11+
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
12+
//
13+
14+
//! ELIPXXXX
15+
//!
16+
//! Implementation of the ELIPXXXX protocol, documented at
17+
//! https://github.com/ElementsProject/ELIPs/blob/main/elip-XXXX.md
18+
//!
19+
20+
use bitcoin::hashes::{sha256t_hash_newtype, Hash};
21+
use bitcoin::secp256k1;
22+
use bitcoin::Network;
23+
use elements::encode::Encodable;
24+
use elements::opcodes;
25+
use elements::script::Builder;
26+
27+
use crate::confidential::{Descriptor as ConfidentialDescriptor, Key};
28+
use crate::descriptor::{DescriptorSecretKey, SinglePriv};
29+
use crate::extensions::{Extension, ParseableExt};
30+
use crate::{Descriptor as OrdinaryDescriptor, DescriptorPublicKey};
31+
32+
/// The SHA-256 initial midstate value for the [`ElipXXXXHash`].
33+
const MIDSTATE_ELIPXXXX: [u8; 32] = [
34+
0x61, 0x1b, 0xb5, 0x5c, 0xc1, 0x14, 0x46, 0x66, 0x6e, 0xeb, 0xb3, 0x39, 0x95, 0x57, 0x7b, 0xb4,
35+
0x4e, 0xe6, 0x6f, 0xf1, 0x14, 0x42, 0x27, 0x70, 0x0f, 0xfe, 0xeb, 0xb1, 0x17, 0x79, 0x98, 0x8d,
36+
];
37+
38+
sha256t_hash_newtype!(
39+
ElipXXXXHash,
40+
ElipXXXXTag,
41+
MIDSTATE_ELIPXXXX,
42+
64,
43+
doc = "ELIP-XXXX Deterministic descriptor blinding keys",
44+
forward
45+
);
46+
47+
impl Key {
48+
pub fn from_elipxxx<T: Extension + ParseableExt>(
49+
descriptor: &OrdinaryDescriptor<DescriptorPublicKey, T>,
50+
network: Network,
51+
) -> Self {
52+
// Handle multi-path
53+
let script_pubkeys: Vec<_> = descriptor
54+
.clone()
55+
.into_single_descriptors()
56+
.expect("valid descriptor")
57+
.iter()
58+
.map(|descriptor| {
59+
// Remove wildcards
60+
descriptor
61+
.at_derivation_index((1 << 31) - 1)
62+
.expect("index not hardened, not multi-path")
63+
.script_pubkey()
64+
})
65+
.collect();
66+
67+
let mut eng = ElipXXXXHash::engine();
68+
for (i, script_pubkey) in script_pubkeys.iter().enumerate() {
69+
if i != 0 {
70+
// Separate script_pubkeys with an invalid opcode
71+
Builder::new()
72+
.push_opcode(opcodes::all::OP_INVALIDOPCODE)
73+
.into_script()
74+
.consensus_encode(&mut eng)
75+
.expect("engines don't error");
76+
}
77+
script_pubkey
78+
.consensus_encode(&mut eng)
79+
.expect("engines don't error");
80+
}
81+
let hash_bytes = ElipXXXXHash::from_engine(eng).to_byte_array();
82+
83+
// This computes mod n
84+
let scalar = secp256k1::scalar::Scalar::from_be_bytes(hash_bytes).expect("bytes from hash");
85+
let secret_key =
86+
secp256k1::SecretKey::from_slice(&scalar.to_be_bytes()).expect("bytes from scalar");
87+
88+
Key::View(DescriptorSecretKey::Single(SinglePriv {
89+
origin: None,
90+
key: bitcoin::key::PrivateKey::new(secret_key, network),
91+
}))
92+
}
93+
}
94+
95+
impl<T: Extension + ParseableExt> ConfidentialDescriptor<DescriptorPublicKey, T> {
96+
pub fn with_elipxxx_descriptor_blinding_key(
97+
descriptor: OrdinaryDescriptor<DescriptorPublicKey, T>,
98+
network: Network,
99+
) -> Self {
100+
ConfidentialDescriptor {
101+
key: Key::from_elipxxx(&descriptor, network),
102+
descriptor,
103+
}
104+
}
105+
}
106+
107+
#[cfg(test)]
108+
mod test {
109+
use super::*;
110+
use crate::descriptor::checksum::desc_checksum;
111+
use std::str::FromStr;
112+
113+
fn add_checksum(desc: &str) -> String {
114+
if desc.find('#').is_some() {
115+
desc.into()
116+
} else {
117+
format!("{}#{}", desc, desc_checksum(desc).unwrap())
118+
}
119+
}
120+
121+
fn confidential_descriptor(
122+
desc: &str,
123+
network: Network,
124+
) -> ConfidentialDescriptor<DescriptorPublicKey> {
125+
let desc = add_checksum(desc);
126+
let desc = OrdinaryDescriptor::<DescriptorPublicKey>::from_str(&desc).unwrap();
127+
ConfidentialDescriptor::with_elipxxx_descriptor_blinding_key(desc, network)
128+
}
129+
130+
fn _first_address(desc: &ConfidentialDescriptor<DescriptorPublicKey>) -> String {
131+
let single_desc = if desc.descriptor.is_multipath() {
132+
let descriptor = desc
133+
.descriptor
134+
.clone()
135+
.into_single_descriptors()
136+
.unwrap()
137+
.first()
138+
.unwrap()
139+
.clone();
140+
ConfidentialDescriptor {
141+
key: desc.key.clone(),
142+
descriptor,
143+
}
144+
} else {
145+
desc.clone()
146+
};
147+
let definite_desc = single_desc
148+
.at_derivation_index(0)
149+
.unwrap();
150+
let secp = elements::secp256k1_zkp::Secp256k1::new();
151+
let params = &elements::AddressParams::ELEMENTS;
152+
definite_desc
153+
.address(&secp, params)
154+
.unwrap()
155+
.to_string()
156+
}
157+
158+
#[test]
159+
fn test_vectors_elipxxxx() {
160+
let network = Network::Bitcoin;
161+
let xpub = "xpub661MyMwAqRbcFkPHucMnrGNzDwb6teAX1RbKQmqtEF8kK3Z7LZ59qafCjB9eCRLiTVG3uxBxgKvRgbubRhqSKXnGGb1aoaqLrpMBDrVxga8";
162+
let pubkey = "03d902f35f560e0470c63313c7369168d9d7df2d49bf295fd9fb7cb109ccee0494";
163+
164+
let mut _i = 0;
165+
for desc in [
166+
&format!("elwpkh({xpub}/<0;1>/*)"),
167+
&format!("elwpkh({xpub}/0/*)"),
168+
&format!("elwpkh({xpub})"),
169+
&format!("elwpkh({pubkey})"),
170+
] {
171+
let _conf_desc = confidential_descriptor(desc, network);
172+
173+
// Uncomment to regenerate test vectors; to see the output, run
174+
// cargo test elipxxxx -- --nocapture
175+
/*
176+
_i = _i + 1;
177+
println!("* Test vector: {}", _i);
178+
println!("** Ordinary descriptor: <code>{}</code>", add_checksum(desc));
179+
println!("** Confidential descriptor (equivalent version): <code>{}</code>", add_checksum(&format!("ct(elipxxxx,{})", desc)));
180+
println!("** Confidential descriptor: <code>{}</code>", _conf_desc);
181+
println!("** Descriptor blinding key: <code>{}</code>", _conf_desc.key);
182+
println!("** First address: <code>{}</code>", _first_address(&_conf_desc))
183+
*/
184+
}
185+
}
186+
}

src/confidential/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
//!
1919
2020
pub mod bare;
21+
pub mod elip_deterministic_descriptor_blinding_key;
2122
pub mod slip77;
2223

2324
use std::fmt;

0 commit comments

Comments
 (0)