|
| 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