Skip to content

Reference implementation of CT descriptors #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Aug 23, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ parse_descriptor_secret,
roundtrip_descriptor,
roundtrip_concrete,
compile_descriptor,
roundtrip_confidential,
]
steps:
- name: Install test dependencies
4 changes: 4 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -44,3 +44,7 @@ path = "fuzz_targets/roundtrip_concrete.rs"
[[bin]]
name = "compile_descriptor"
path = "fuzz_targets/compile_descriptor.rs"

[[bin]]
name = "roundtrip_confidential"
path = "fuzz_targets/roundtrip_confidential.rs"
38 changes: 38 additions & 0 deletions fuzz/fuzz_targets/roundtrip_confidential.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
extern crate elements_miniscript as miniscript;
extern crate regex;

use std::str::FromStr;

use miniscript::confidential;

fn do_test(data: &[u8]) {
// This is how we test in rust-miniscript. It is difficult to enforce wrapping logic in fuzzer
// for alias like t: and_v(1), likely and unlikely.
// Just directly check whether the inferred descriptor is the same.
let s = String::from_utf8_lossy(data);
if let Ok(desc) = confidential::Descriptor::<String>::from_str(&s) {
let str2 = desc.to_string();
let desc2 = confidential::Descriptor::<String>::from_str(&str2).unwrap();

assert_eq!(desc.to_string(), desc2.to_string());
}
}

fn main() {
loop {
honggfuzz::fuzz!(|data| {
do_test(data);
});
}
}

#[cfg(test)]
mod tests {
use miniscript::elements::hex::FromHex;

#[test]
fn duplicate_crash() {
let hex = Vec::<u8>::from_hex("00").unwrap();
super::do_test(&hex);
}
}
122 changes: 122 additions & 0 deletions src/confidential/bare.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Miniscript
// Written in 2023 by
// Andrew Poelstra <[email protected]>
//
// 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/>.
//

//! "Bare Key" Confidential Descriptors

use bitcoin::hashes::{sha256t_hash_newtype, Hash};
use elements::encode::Encodable;
use elements::secp256k1_zkp;

use crate::ToPublicKey;

/// The SHA-256 initial midstate value for the [`TweakHash`].
const MIDSTATE_HASH_TO_PRIVATE_HASH: [u8; 32] = [
0x2f, 0x85, 0x61, 0xec, 0x30, 0x88, 0xad, 0xa9, 0x5a, 0xe7, 0x43, 0xcd, 0x3c, 0x5f, 0x59, 0x7d,
0xc0, 0x4b, 0xd0, 0x7f, 0x06, 0x5f, 0x1c, 0x06, 0x47, 0x89, 0x36, 0x63, 0xf3, 0x92, 0x6e, 0x65,
];

sha256t_hash_newtype!(
TweakHash,
TweakTag,
MIDSTATE_HASH_TO_PRIVATE_HASH,
64,
doc = "BIP-340 Tagged hash for tweaking blinding keys",
forward
);

/// Tweaks a bare key using the scriptPubKey of a descriptor
pub fn tweak_key<'a, Pk, V>(
secp: &secp256k1_zkp::Secp256k1<V>,
spk: &elements::Script,
pk: &Pk,
) -> secp256k1_zkp::PublicKey
where
Pk: ToPublicKey + 'a,
V: secp256k1_zkp::Verification,
{
let mut eng = TweakHash::engine();
pk.to_public_key()
.write_into(&mut eng)
.expect("engines don't error");
spk.consensus_encode(&mut eng).expect("engines don't error");
let hash_bytes = TweakHash::from_engine(eng).to_byte_array();
let hash_scalar = secp256k1_zkp::Scalar::from_be_bytes(hash_bytes).expect("bytes from hash");
pk.to_public_key()
.inner
.add_exp_tweak(secp, &hash_scalar)
.unwrap()
}

/// Tweaks a bare key using the scriptPubKey of a descriptor
pub fn tweak_private_key<'a, Pk, V>(
secp: &secp256k1_zkp::Secp256k1<V>,
spk: &elements::Script,
pk: &Pk,
) -> secp256k1_zkp::PublicKey
where
Pk: ToPublicKey + 'a,
V: secp256k1_zkp::Verification,
{
let mut eng = TweakHash::engine();
pk.to_public_key()
.write_into(&mut eng)
.expect("engines don't error");
spk.consensus_encode(&mut eng).expect("engines don't error");
let hash_bytes = TweakHash::from_engine(eng).to_byte_array();
let hash_scalar = secp256k1_zkp::Scalar::from_be_bytes(hash_bytes).expect("bytes from hash");
pk.to_public_key()
.inner
.add_exp_tweak(secp, &hash_scalar)
.unwrap()
}

#[cfg(test)]
mod tests {
use bitcoin::hashes::sha256t::Tag;
use bitcoin::hashes::{sha256, HashEngine};

use super::*;

#[test]
fn tagged_hash() {
// Check that cached midstate is computed correctly
// This code taken from `tag_engine` in the rust-bitcoin tests; it is identical
// to that used by the BIP-0340 hashes in Taproot
let mut engine = sha256::Hash::engine();
let tag_hash = sha256::Hash::hash(b"CT-Blinding-Key/1.0");
engine.input(&tag_hash[..]);
engine.input(&tag_hash[..]);
assert_eq!(
MIDSTATE_HASH_TO_PRIVATE_HASH,
engine.midstate().to_byte_array()
);

// Test empty hash
assert_eq!(
TweakHash::from_engine(TweakTag::engine()).to_string(),
"d12a140aca856fbb917b931f263c42f064608985e2ce17ae5157daa17c55e8d9",
);
assert_eq!(
TweakHash::hash(&[]).to_string(),
"d12a140aca856fbb917b931f263c42f064608985e2ce17ae5157daa17c55e8d9",
);

// And hash of 100 bytes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: 80 bytes

let data: Vec<u8> = (0..80).collect();
assert_eq!(
TweakHash::hash(&data).to_string(),
"e1e52419a2934d278c50e29608969d2f23c1bd1243a09bfc8026d4ed4b085e39",
);
}
}
Loading