Skip to content

Commit 259f7c4

Browse files
committed
Merge #61: Add ELIP-deterministic-descriptor-blinding-key module
34005bc Add ELIP-151 module (Leonardo Comandini) Pull request description: Reference implementation for ElementsProject/ELIPs#8 ACKs for top commit: apoelstra: ACK 34005bc Tree-SHA512: 8f376d246f6240820606dc555401e94bfadc92614644c3eeafde57674bc62b8e20cde766632540325ceb4e35e51a7006d9bdf175e5b9eb3948a5f5fe56f137eb
2 parents 353005c + 34005bc commit 259f7c4

File tree

2 files changed

+212
-0
lines changed

2 files changed

+212
-0
lines changed

src/confidential/elip151.rs

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
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+
//! ELIP151
15+
//!
16+
//! Implementation of the ELIP151 protocol, documented at
17+
//! https://github.com/ElementsProject/ELIPs/blob/main/elip-0151.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, Error};
31+
32+
/// The SHA-256 initial midstate value for the [`Elip151Hash`].
33+
const MIDSTATE_ELIP151: [u8; 32] = [
34+
0x49, 0x81, 0x61, 0xd8, 0x52, 0x45, 0xf7, 0xaa, 0xd8, 0x24, 0x27, 0xb5, 0x64, 0x69, 0xe7, 0xd6,
35+
0x98, 0x17, 0xeb, 0x0f, 0x27, 0x14, 0x6f, 0x4e, 0x7b, 0x95, 0xb3, 0x6e, 0x46, 0xc1, 0xb5, 0x61,
36+
];
37+
38+
sha256t_hash_newtype!(
39+
Elip151Hash,
40+
Elip151Tag,
41+
MIDSTATE_ELIP151,
42+
64,
43+
doc = "ELIP-151 Deterministic descriptor blinding keys",
44+
forward
45+
);
46+
47+
impl Key {
48+
pub fn from_elip151<T: Extension + ParseableExt>(
49+
descriptor: &OrdinaryDescriptor<DescriptorPublicKey, T>,
50+
) -> Result<Self, Error> {
51+
if !descriptor.has_wildcard() {
52+
return Err(Error::Unexpected(
53+
"Descriptors without wildcards are not supported in elip151".into(),
54+
));
55+
}
56+
57+
// Handle multi-path
58+
let script_pubkeys: Vec<_> = descriptor
59+
.clone()
60+
.into_single_descriptors()
61+
.expect("valid descriptor")
62+
.iter()
63+
.map(|descriptor| {
64+
// Remove wildcards
65+
descriptor
66+
.at_derivation_index((1 << 31) - 1)
67+
.expect("index not hardened, not multi-path")
68+
.script_pubkey()
69+
})
70+
.collect();
71+
72+
let mut eng = Elip151Hash::engine();
73+
for script_pubkey in script_pubkeys {
74+
Builder::new()
75+
.push_opcode(opcodes::all::OP_INVALIDOPCODE)
76+
.into_script()
77+
.consensus_encode(&mut eng)
78+
.expect("engines don't error");
79+
script_pubkey
80+
.consensus_encode(&mut eng)
81+
.expect("engines don't error");
82+
}
83+
let hash_bytes = Elip151Hash::from_engine(eng).to_byte_array();
84+
85+
// This computes mod n
86+
let scalar = secp256k1::scalar::Scalar::from_be_bytes(hash_bytes).expect("bytes from hash");
87+
let secret_key =
88+
secp256k1::SecretKey::from_slice(&scalar.to_be_bytes()).expect("bytes from scalar");
89+
90+
// Single view keys are displayed as hex (not WIF) so we can choose any netowrk here
91+
let network = Network::Bitcoin;
92+
Ok(Key::View(DescriptorSecretKey::Single(SinglePriv {
93+
origin: None,
94+
key: bitcoin::key::PrivateKey::new(secret_key, network),
95+
})))
96+
}
97+
}
98+
99+
impl<T: Extension + ParseableExt> ConfidentialDescriptor<DescriptorPublicKey, T> {
100+
pub fn with_elip151_descriptor_blinding_key(
101+
descriptor: OrdinaryDescriptor<DescriptorPublicKey, T>,
102+
) -> Result<Self, Error> {
103+
Ok(ConfidentialDescriptor {
104+
key: Key::from_elip151(&descriptor)?,
105+
descriptor,
106+
})
107+
}
108+
}
109+
110+
#[cfg(test)]
111+
mod test {
112+
use super::*;
113+
use crate::descriptor::checksum::desc_checksum;
114+
use bitcoin::hashes::{sha256, HashEngine};
115+
use std::str::FromStr;
116+
117+
#[test]
118+
fn tagged_hash_elip151() {
119+
// Check that cached midstate is computed correctly, code from rust-bitcoin
120+
let mut engine = sha256::Hash::engine();
121+
let tag_hash = sha256::Hash::hash(b"Deterministic-View-Key/1.0");
122+
engine.input(&tag_hash[..]);
123+
engine.input(&tag_hash[..]);
124+
assert_eq!(MIDSTATE_ELIP151, engine.midstate().to_byte_array());
125+
}
126+
127+
fn add_checksum(desc: &str) -> String {
128+
if desc.find('#').is_some() {
129+
desc.into()
130+
} else {
131+
format!("{}#{}", desc, desc_checksum(desc).unwrap())
132+
}
133+
}
134+
135+
fn confidential_descriptor(
136+
desc: &str,
137+
) -> Result<ConfidentialDescriptor<DescriptorPublicKey>, Error> {
138+
let desc = add_checksum(desc);
139+
let desc = OrdinaryDescriptor::<DescriptorPublicKey>::from_str(&desc).unwrap();
140+
ConfidentialDescriptor::with_elip151_descriptor_blinding_key(desc)
141+
}
142+
143+
fn _first_address(desc: &ConfidentialDescriptor<DescriptorPublicKey>) -> String {
144+
let single_desc = if desc.descriptor.is_multipath() {
145+
let descriptor = desc
146+
.descriptor
147+
.clone()
148+
.into_single_descriptors()
149+
.unwrap()
150+
.first()
151+
.unwrap()
152+
.clone();
153+
ConfidentialDescriptor {
154+
key: desc.key.clone(),
155+
descriptor,
156+
}
157+
} else {
158+
desc.clone()
159+
};
160+
let definite_desc = single_desc.at_derivation_index(0).unwrap();
161+
let secp = elements::secp256k1_zkp::Secp256k1::new();
162+
let params = &elements::AddressParams::ELEMENTS;
163+
definite_desc.address(&secp, params).unwrap().to_string()
164+
}
165+
166+
#[test]
167+
fn test_vectors_elip151() {
168+
let xpub = "xpub661MyMwAqRbcFkPHucMnrGNzDwb6teAX1RbKQmqtEF8kK3Z7LZ59qafCjB9eCRLiTVG3uxBxgKvRgbubRhqSKXnGGb1aoaqLrpMBDrVxga8";
169+
let pubkey = "03d902f35f560e0470c63313c7369168d9d7df2d49bf295fd9fb7cb109ccee0494";
170+
171+
let mut _i = 0;
172+
for desc in [
173+
&format!("elwpkh({xpub}/<0;1>/*)"),
174+
&format!("elwpkh({xpub}/0/*)"),
175+
] {
176+
let conf_desc = confidential_descriptor(desc).unwrap();
177+
let elip151_desc = add_checksum(&format!("ct(elip151,{})", desc));
178+
let conf_desc_elip151 = ConfidentialDescriptor::<DescriptorPublicKey>::from_str(&elip151_desc).unwrap();
179+
assert_eq!(conf_desc, conf_desc_elip151);
180+
181+
// Uncomment this and below to regenerate test vectors; to see the output, run
182+
// cargo test test_vectors_elip151 -- --nocapture
183+
/*
184+
_i = _i + 1;
185+
println!("* Test vector {}", _i);
186+
println!("** Ordinary descriptor: <code>{}</code>", add_checksum(desc));
187+
println!("** Derived descriptor blinding key: <code>{}</code>", conf_desc.key);
188+
println!("** Derived confidential descriptor: <code>{}</code>", conf_desc);
189+
println!("** Derived confidential descriptor (equivalent version): <code>{}</code>", elip151_desc);
190+
println!("** First address: <code>{}</code>", _first_address(&conf_desc))
191+
*/
192+
}
193+
194+
_i = 0;
195+
for invalid_desc in [&format!("elwpkh({xpub})"), &format!("elwpkh({pubkey})")] {
196+
let err = confidential_descriptor(invalid_desc).unwrap_err();
197+
let text = "Descriptors without wildcards are not supported in elip151".to_string();
198+
assert_eq!(err, Error::Unexpected(text));
199+
/*
200+
_i = _i + 1;
201+
println!("* Invalid Test vector {}", _i);
202+
println!("** Ordinary descriptor: <code>{}</code>", add_checksum(invalid_desc));
203+
println!("** Invalid confidential descriptor: <code>{}</code>", add_checksum(&format!("ct(elip151,{})", invalid_desc)));
204+
*/
205+
}
206+
}
207+
}

src/confidential/mod.rs

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

2324
use std::fmt;
@@ -216,6 +217,10 @@ impl_from_str!(
216217
let keyexpr = &top.args[0];
217218
Ok(Descriptor {
218219
key: match (keyexpr.name, keyexpr.args.len()) {
220+
("elip151", 0) => {
221+
let d = crate::Descriptor::<DescriptorPublicKey>::from_tree(&top.args[1])?;
222+
Key::from_elip151(&d)?
223+
}
219224
("slip77", 1) => Key::Slip77(expression::terminal(&keyexpr.args[0], slip77::MasterBlindingKey::from_str)?),
220225
("slip77", _) => return Err(Error::BadDescriptor(
221226
"slip77() must have exactly one argument".to_owned()

0 commit comments

Comments
 (0)