Skip to content

Commit bf28d9b

Browse files
committed
WIP: Add ELIP-deterministic-descriptor-blinding-key module
1 parent af410f6 commit bf28d9b

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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+
25+
use crate::confidential::{Descriptor as ConfidentialDescriptor, Key};
26+
use crate::descriptor::{DescriptorSecretKey, SinglePriv};
27+
use crate::extensions::{Extension, ParseableExt};
28+
use crate::{Descriptor as OrdinaryDescriptor, DescriptorPublicKey};
29+
30+
/// The SHA-256 initial midstate value for the [`ElipXXXXHash`].
31+
const MIDSTATE_ELIPXXXX: [u8; 32] = [
32+
0x61, 0x1b, 0xb5, 0x5c, 0xc1, 0x14, 0x46, 0x66, 0x6e, 0xeb, 0xb3, 0x39, 0x95, 0x57, 0x7b, 0xb4,
33+
0x4e, 0xe6, 0x6f, 0xf1, 0x14, 0x42, 0x27, 0x70, 0x0f, 0xfe, 0xeb, 0xb1, 0x17, 0x79, 0x98, 0x8d,
34+
];
35+
36+
sha256t_hash_newtype!(
37+
ElipXXXXHash,
38+
ElipXXXXTag,
39+
MIDSTATE_ELIPXXXX,
40+
64,
41+
doc = "ELIP-XXXX Deterministic descriptor blinding keys",
42+
forward
43+
);
44+
45+
impl Key {
46+
pub fn from_elipxxx<T: Extension + ParseableExt>(
47+
descriptor: &OrdinaryDescriptor<DescriptorPublicKey, T>,
48+
network: Network,
49+
) -> Self {
50+
// Handle multi-path
51+
let script_pubkeys: Vec<_> = descriptor
52+
.clone()
53+
.into_single_descriptors()
54+
.expect("valid descriptor")
55+
.iter()
56+
.map(|descriptor| {
57+
// Remove wildcards
58+
descriptor
59+
.at_derivation_index((1 << 31) - 1)
60+
.expect("index not hardened, not multi-path")
61+
.script_pubkey()
62+
})
63+
.collect();
64+
65+
let mut eng = ElipXXXXHash::engine();
66+
for script_pubkey in script_pubkeys {
67+
script_pubkey
68+
.consensus_encode(&mut eng)
69+
.expect("engines don't error");
70+
}
71+
let hash_bytes = ElipXXXXHash::from_engine(eng).to_byte_array();
72+
73+
// This computes mod n
74+
let scalar = secp256k1::scalar::Scalar::from_be_bytes(hash_bytes).expect("bytes from hash");
75+
let secret_key =
76+
secp256k1::SecretKey::from_slice(&scalar.to_be_bytes()).expect("bytes from scalar");
77+
78+
Key::View(DescriptorSecretKey::Single(SinglePriv {
79+
origin: None,
80+
key: bitcoin::key::PrivateKey::new(secret_key, network),
81+
}))
82+
}
83+
}
84+
85+
impl<T: Extension + ParseableExt> ConfidentialDescriptor<DescriptorPublicKey, T> {
86+
pub fn with_elipxxx_descriptor_blinding_key(
87+
descriptor: OrdinaryDescriptor<DescriptorPublicKey, T>,
88+
network: Network,
89+
) -> Self {
90+
ConfidentialDescriptor {
91+
key: Key::from_elipxxx(&descriptor, network),
92+
descriptor,
93+
}
94+
}
95+
}
96+
97+
#[cfg(test)]
98+
mod test {
99+
use super::*;
100+
use crate::descriptor::checksum::desc_checksum;
101+
use std::str::FromStr;
102+
103+
fn add_checksum(desc: &str) -> String {
104+
if desc.find('#').is_some() {
105+
desc.into()
106+
} else {
107+
format!("{}#{}", desc, desc_checksum(desc).unwrap())
108+
}
109+
}
110+
111+
fn confidential_descriptor(
112+
desc: &str,
113+
network: Network,
114+
) -> ConfidentialDescriptor<DescriptorPublicKey> {
115+
let desc = add_checksum(desc);
116+
let desc = OrdinaryDescriptor::<DescriptorPublicKey>::from_str(&desc).unwrap();
117+
ConfidentialDescriptor::with_elipxxx_descriptor_blinding_key(desc, network)
118+
}
119+
120+
#[test]
121+
fn test_vectors_elipxxxx() {
122+
let network = Network::Bitcoin;
123+
let xpub = "xpub661MyMwAqRbcFkPHucMnrGNzDwb6teAX1RbKQmqtEF8kK3Z7LZ59qafCjB9eCRLiTVG3uxBxgKvRgbubRhqSKXnGGb1aoaqLrpMBDrVxga8";
124+
let pubkey = "03d902f35f560e0470c63313c7369168d9d7df2d49bf295fd9fb7cb109ccee0494";
125+
126+
let mut _i = 0;
127+
for desc in [
128+
&format!("elwpkh({xpub}/<0;1>/*)"),
129+
&format!("elwpkh({xpub}/0/*)"),
130+
&format!("elwpkh({xpub})"),
131+
&format!("elwpkh({pubkey})"),
132+
] {
133+
let desc = &add_checksum(desc);
134+
let _conf_desc = confidential_descriptor(desc, network);
135+
// Uncomment to regenerate test vectors; to see the output, run
136+
// cargo test elipxxxx -- --nocapture
137+
/*
138+
_i = _i + 1;
139+
println!("* Test vector: {}", _i);
140+
println!("** Ordinary descriptor: {}", desc.to_string());
141+
println!("** Confidential descriptor: {}", _conf_desc.to_string());
142+
println!("** Descriptor blinding key: {}", _conf_desc.key.to_string());
143+
*/
144+
}
145+
}
146+
}

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)