Skip to content

Commit f887126

Browse files
committed
Merge #31: Reference implementation of CT descriptors
725f450 confidential: hack in view descriptor support (Andrew Poelstra) 6359f03 confidential: switch test vectors to use xpubs rather than bare keys (Andrew Poelstra) a332208 re-export confidential::Descirptor as ConfidentialDescirptor (Andrew Poelstra) 694628f CT descriptors: add fuzz test (Andrew Poelstra) 2b5bd55 CT descriptors: generate ELIP test vectors (Andrew Poelstra) 604c7e3 CT descriptors: add test vectors (Andrew Poelstra) e9ac0bc CT descriptors: add `bare` module (Andrew Poelstra) f5b2fe5 CT descriptors: add `slip77` module (Andrew Poelstra) 277b1a5 CT descriptors: block in module (Andrew Poelstra) 450d4d1 expression: stop special-casing the text `slip77` (Andrew Poelstra) 5d2755d delete descriptor::Blinded (Andrew Poelstra) 67e9422 descriptor: expose checksum function to whole crate (Andrew Poelstra) b11c818 descriptor: fix Display impl for legacy CSFS covenants (Andrew Poelstra) Pull request description: I'm aware that there are already a basic form of CT descriptors in this library, and this does not remove them, it just adds a new module which should act as a reference implementation for ElementsProject/ELIPs#1 WIP because it needs a new rust-elements release to get tagged hashes in `bitcoin_hashes`. ACKs for top commit: sanket1729: ACK 725f450. Tree-SHA512: 52d5961e653f19a6378f6abfbf812cedfb295e56cc08ea6ab8c80cdd5c0ee3b8da209f7c65c87193bf42448c0d5db86ead27dbe0bcb1e245f6c3f0b9ccce0f11
2 parents a832fda + 725f450 commit f887126

File tree

14 files changed

+739
-228
lines changed

14 files changed

+739
-228
lines changed

.github/workflows/fuzz.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ parse_descriptor_secret,
2424
roundtrip_descriptor,
2525
roundtrip_concrete,
2626
compile_descriptor,
27+
roundtrip_confidential,
2728
]
2829
steps:
2930
- name: Install test dependencies

fuzz/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,7 @@ path = "fuzz_targets/roundtrip_concrete.rs"
4444
[[bin]]
4545
name = "compile_descriptor"
4646
path = "fuzz_targets/compile_descriptor.rs"
47+
48+
[[bin]]
49+
name = "roundtrip_confidential"
50+
path = "fuzz_targets/roundtrip_confidential.rs"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
extern crate elements_miniscript as miniscript;
2+
extern crate regex;
3+
4+
use std::str::FromStr;
5+
6+
use miniscript::confidential;
7+
8+
fn do_test(data: &[u8]) {
9+
// This is how we test in rust-miniscript. It is difficult to enforce wrapping logic in fuzzer
10+
// for alias like t: and_v(1), likely and unlikely.
11+
// Just directly check whether the inferred descriptor is the same.
12+
let s = String::from_utf8_lossy(data);
13+
if let Ok(desc) = confidential::Descriptor::<String>::from_str(&s) {
14+
let str2 = desc.to_string();
15+
let desc2 = confidential::Descriptor::<String>::from_str(&str2).unwrap();
16+
17+
assert_eq!(desc.to_string(), desc2.to_string());
18+
}
19+
}
20+
21+
fn main() {
22+
loop {
23+
honggfuzz::fuzz!(|data| {
24+
do_test(data);
25+
});
26+
}
27+
}
28+
29+
#[cfg(test)]
30+
mod tests {
31+
use miniscript::elements::hex::FromHex;
32+
33+
#[test]
34+
fn duplicate_crash() {
35+
let hex = Vec::<u8>::from_hex("00").unwrap();
36+
super::do_test(&hex);
37+
}
38+
}

src/confidential/bare.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Miniscript
2+
// Written in 2023 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+
//! "Bare Key" Confidential Descriptors
16+
17+
use bitcoin::hashes::{sha256t_hash_newtype, Hash};
18+
use elements::encode::Encodable;
19+
use elements::secp256k1_zkp;
20+
21+
use crate::ToPublicKey;
22+
23+
/// The SHA-256 initial midstate value for the [`TweakHash`].
24+
const MIDSTATE_HASH_TO_PRIVATE_HASH: [u8; 32] = [
25+
0x2f, 0x85, 0x61, 0xec, 0x30, 0x88, 0xad, 0xa9, 0x5a, 0xe7, 0x43, 0xcd, 0x3c, 0x5f, 0x59, 0x7d,
26+
0xc0, 0x4b, 0xd0, 0x7f, 0x06, 0x5f, 0x1c, 0x06, 0x47, 0x89, 0x36, 0x63, 0xf3, 0x92, 0x6e, 0x65,
27+
];
28+
29+
sha256t_hash_newtype!(
30+
TweakHash,
31+
TweakTag,
32+
MIDSTATE_HASH_TO_PRIVATE_HASH,
33+
64,
34+
doc = "BIP-340 Tagged hash for tweaking blinding keys",
35+
forward
36+
);
37+
38+
/// Tweaks a bare key using the scriptPubKey of a descriptor
39+
pub fn tweak_key<'a, Pk, V>(
40+
secp: &secp256k1_zkp::Secp256k1<V>,
41+
spk: &elements::Script,
42+
pk: &Pk,
43+
) -> secp256k1_zkp::PublicKey
44+
where
45+
Pk: ToPublicKey + 'a,
46+
V: secp256k1_zkp::Verification,
47+
{
48+
let mut eng = TweakHash::engine();
49+
pk.to_public_key()
50+
.write_into(&mut eng)
51+
.expect("engines don't error");
52+
spk.consensus_encode(&mut eng).expect("engines don't error");
53+
let hash_bytes = TweakHash::from_engine(eng).to_byte_array();
54+
let hash_scalar = secp256k1_zkp::Scalar::from_be_bytes(hash_bytes).expect("bytes from hash");
55+
pk.to_public_key()
56+
.inner
57+
.add_exp_tweak(secp, &hash_scalar)
58+
.unwrap()
59+
}
60+
61+
/// Tweaks a bare key using the scriptPubKey of a descriptor
62+
pub fn tweak_private_key<'a, Pk, V>(
63+
secp: &secp256k1_zkp::Secp256k1<V>,
64+
spk: &elements::Script,
65+
pk: &Pk,
66+
) -> secp256k1_zkp::PublicKey
67+
where
68+
Pk: ToPublicKey + 'a,
69+
V: secp256k1_zkp::Verification,
70+
{
71+
let mut eng = TweakHash::engine();
72+
pk.to_public_key()
73+
.write_into(&mut eng)
74+
.expect("engines don't error");
75+
spk.consensus_encode(&mut eng).expect("engines don't error");
76+
let hash_bytes = TweakHash::from_engine(eng).to_byte_array();
77+
let hash_scalar = secp256k1_zkp::Scalar::from_be_bytes(hash_bytes).expect("bytes from hash");
78+
pk.to_public_key()
79+
.inner
80+
.add_exp_tweak(secp, &hash_scalar)
81+
.unwrap()
82+
}
83+
84+
#[cfg(test)]
85+
mod tests {
86+
use bitcoin::hashes::sha256t::Tag;
87+
use bitcoin::hashes::{sha256, HashEngine};
88+
89+
use super::*;
90+
91+
#[test]
92+
fn tagged_hash() {
93+
// Check that cached midstate is computed correctly
94+
// This code taken from `tag_engine` in the rust-bitcoin tests; it is identical
95+
// to that used by the BIP-0340 hashes in Taproot
96+
let mut engine = sha256::Hash::engine();
97+
let tag_hash = sha256::Hash::hash(b"CT-Blinding-Key/1.0");
98+
engine.input(&tag_hash[..]);
99+
engine.input(&tag_hash[..]);
100+
assert_eq!(
101+
MIDSTATE_HASH_TO_PRIVATE_HASH,
102+
engine.midstate().to_byte_array()
103+
);
104+
105+
// Test empty hash
106+
assert_eq!(
107+
TweakHash::from_engine(TweakTag::engine()).to_string(),
108+
"d12a140aca856fbb917b931f263c42f064608985e2ce17ae5157daa17c55e8d9",
109+
);
110+
assert_eq!(
111+
TweakHash::hash(&[]).to_string(),
112+
"d12a140aca856fbb917b931f263c42f064608985e2ce17ae5157daa17c55e8d9",
113+
);
114+
115+
// And hash of 100 bytes
116+
let data: Vec<u8> = (0..80).collect();
117+
assert_eq!(
118+
TweakHash::hash(&data).to_string(),
119+
"e1e52419a2934d278c50e29608969d2f23c1bd1243a09bfc8026d4ed4b085e39",
120+
);
121+
}
122+
}

0 commit comments

Comments
 (0)