Skip to content

Commit

Permalink
Add ELIP-deterministic-descriptor-blinding-key module
Browse files Browse the repository at this point in the history
  • Loading branch information
LeoComandini committed Nov 23, 2023
1 parent af410f6 commit e3b775c
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 0 deletions.
185 changes: 185 additions & 0 deletions src/confidential/elip_deterministic_descriptor_blinding_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Miniscript
// Written in 2023 by Leonardo Comandini
//
// To the extent possible under law, the author(s) have dedicated all
// copyright and related and neighboring rights to this software to
// the public domain worldwide. This software is distributed without
// any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication
// along with this software.
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
//

//! ELIPXXXX
//!
//! Implementation of the ELIPXXXX protocol, documented at
//! https://github.com/ElementsProject/ELIPs/blob/main/elip-XXXX.md
//!
use bitcoin::hashes::{sha256t_hash_newtype, Hash};
use bitcoin::secp256k1;
use bitcoin::Network;
use elements::encode::Encodable;
use elements::opcodes;
use elements::script::Builder;

use crate::confidential::{Descriptor as ConfidentialDescriptor, Key};
use crate::descriptor::{DescriptorSecretKey, SinglePriv};
use crate::extensions::{Extension, ParseableExt};
use crate::{Descriptor as OrdinaryDescriptor, DescriptorPublicKey};

/// The SHA-256 initial midstate value for the [`ElipXXXXHash`].
const MIDSTATE_ELIPXXXX: [u8; 32] = [
0x61, 0x1b, 0xb5, 0x5c, 0xc1, 0x14, 0x46, 0x66, 0x6e, 0xeb, 0xb3, 0x39, 0x95, 0x57, 0x7b, 0xb4,
0x4e, 0xe6, 0x6f, 0xf1, 0x14, 0x42, 0x27, 0x70, 0x0f, 0xfe, 0xeb, 0xb1, 0x17, 0x79, 0x98, 0x8d,
];

sha256t_hash_newtype!(
ElipXXXXHash,
ElipXXXXTag,
MIDSTATE_ELIPXXXX,
64,
doc = "ELIP-XXXX Deterministic descriptor blinding keys",
forward
);

impl Key {
pub fn from_elipxxx<T: Extension + ParseableExt>(
descriptor: &OrdinaryDescriptor<DescriptorPublicKey, T>,
network: Network,
) -> Self {
// Handle multi-path
let script_pubkeys: Vec<_> = descriptor
.clone()
.into_single_descriptors()
.expect("valid descriptor")
.iter()
.map(|descriptor| {
// Remove wildcards
descriptor
.at_derivation_index((1 << 31) - 1)
.expect("index not hardened, not multi-path")
.script_pubkey()
})
.collect();

let mut eng = ElipXXXXHash::engine();
for (i, script_pubkey) in script_pubkeys.iter().enumerate() {
if i != 0 {
// Separate script_pubkeys with an invalid opcode
Builder::new()
.push_opcode(opcodes::all::OP_INVALIDOPCODE)
.into_script()
.consensus_encode(&mut eng)
.expect("engines don't error");
}
script_pubkey
.consensus_encode(&mut eng)
.expect("engines don't error");
}
let hash_bytes = ElipXXXXHash::from_engine(eng).to_byte_array();

// This computes mod n
let scalar = secp256k1::scalar::Scalar::from_be_bytes(hash_bytes).expect("bytes from hash");
let secret_key =
secp256k1::SecretKey::from_slice(&scalar.to_be_bytes()).expect("bytes from scalar");

Key::View(DescriptorSecretKey::Single(SinglePriv {
origin: None,
key: bitcoin::key::PrivateKey::new(secret_key, network),
}))
}
}

impl<T: Extension + ParseableExt> ConfidentialDescriptor<DescriptorPublicKey, T> {
pub fn with_elipxxx_descriptor_blinding_key(
descriptor: OrdinaryDescriptor<DescriptorPublicKey, T>,
network: Network,
) -> Self {
ConfidentialDescriptor {
key: Key::from_elipxxx(&descriptor, network),
descriptor,
}
}
}

#[cfg(test)]
mod test {
use super::*;
use crate::descriptor::checksum::desc_checksum;
use std::str::FromStr;

fn add_checksum(desc: &str) -> String {
if desc.find('#').is_some() {
desc.into()
} else {
format!("{}#{}", desc, desc_checksum(desc).unwrap())
}
}

fn confidential_descriptor(
desc: &str,
network: Network,
) -> ConfidentialDescriptor<DescriptorPublicKey> {
let desc = add_checksum(desc);
let desc = OrdinaryDescriptor::<DescriptorPublicKey>::from_str(&desc).unwrap();
ConfidentialDescriptor::with_elipxxx_descriptor_blinding_key(desc, network)
}

fn _first_address(desc: &ConfidentialDescriptor<DescriptorPublicKey>) -> String {
let single_desc = if desc.descriptor.is_multipath() {
let descriptor = desc
.descriptor
.clone()
.into_single_descriptors()
.unwrap()
.first()
.unwrap()
.clone();
ConfidentialDescriptor {
key: desc.key.clone(),
descriptor,
}
} else {
desc.clone()
};
let definite_desc = single_desc
.at_derivation_index(0)
.unwrap();
let secp = elements::secp256k1_zkp::Secp256k1::new();
let params = &elements::AddressParams::ELEMENTS;
definite_desc
.address(&secp, params)
.unwrap()
.to_string()
}

#[test]
fn test_vectors_elipxxxx() {
let network = Network::Bitcoin;
let xpub = "xpub661MyMwAqRbcFkPHucMnrGNzDwb6teAX1RbKQmqtEF8kK3Z7LZ59qafCjB9eCRLiTVG3uxBxgKvRgbubRhqSKXnGGb1aoaqLrpMBDrVxga8";
let pubkey = "03d902f35f560e0470c63313c7369168d9d7df2d49bf295fd9fb7cb109ccee0494";

let mut _i = 0;
for desc in [
&format!("elwpkh({xpub}/<0;1>/*)"),
&format!("elwpkh({xpub}/0/*)"),
&format!("elwpkh({xpub})"),
&format!("elwpkh({pubkey})"),
] {
let desc = &add_checksum(desc);
let _conf_desc = confidential_descriptor(desc, network);
// Uncomment to regenerate test vectors; to see the output, run
// cargo test elipxxxx -- --nocapture
/*
_i = _i + 1;
println!("* Test vector: {}", _i);
println!("** Ordinary descriptor: <code>{}</code>", desc.to_string());
println!("** Confidential descriptor: <code>{}</code>", _conf_desc.to_string());
println!("** Descriptor blinding key: <code>{}</code>", _conf_desc.key.to_string());
println!("** First address: <code>{}</code>", _first_address(&_conf_desc))
*/
}
}
}
1 change: 1 addition & 0 deletions src/confidential/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
//!
pub mod bare;
pub mod elip_deterministic_descriptor_blinding_key;
pub mod slip77;

use std::fmt;
Expand Down

0 comments on commit e3b775c

Please sign in to comment.