Skip to content

Commit 803b5fe

Browse files
committed
P2TR address from untweaked public key
Ambiguous TweakedPublicKey and UntweakedPublicKey type aliases and methods to convert Use structs for Untweaked and Tweaked key type swap dangerous api to work on tweaked keys remove unecessary allocations and rename methods Use type alias for UntweakedPublicKey TweakedPublicKey::new(...) method added minor naming and doc changes
1 parent a961ab4 commit 803b5fe

2 files changed

Lines changed: 100 additions & 4 deletions

File tree

src/util/address.rs

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use core::num::ParseIntError;
4040
use core::str::FromStr;
4141
#[cfg(feature = "std")] use std::error;
4242

43-
use secp256k1::schnorrsig;
43+
use secp256k1::{Secp256k1, Verification};
4444
use bech32;
4545
use hashes::Hash;
4646
use hash_types::{PubkeyHash, WPubkeyHash, ScriptHash, WScriptHash};
@@ -49,7 +49,9 @@ use blockdata::constants::{PUBKEY_ADDRESS_PREFIX_MAIN, SCRIPT_ADDRESS_PREFIX_MAI
4949
use network::constants::Network;
5050
use util::base58;
5151
use util::ecdsa;
52+
use util::taproot::TapBranchHash;
5253
use blockdata::script::Instruction;
54+
use util::schnorr::{TapTweak, UntweakedPublicKey, TweakedPublicKey};
5355

5456
/// Address error.
5557
#[derive(Debug, PartialEq, Eq, Clone)]
@@ -511,13 +513,34 @@ impl Address {
511513
}
512514
}
513515

514-
/// Create a pay to taproot address
515-
pub fn p2tr(taptweaked_key: schnorrsig::PublicKey, network: Network) -> Address {
516+
/// Create a pay to taproot address from untweaked key
517+
pub fn p2tr<C: Verification>(
518+
secp: Secp256k1<C>,
519+
internal_key: UntweakedPublicKey,
520+
merkle_root: Option<TapBranchHash>,
521+
network: Network
522+
) -> Address {
516523
Address {
517524
network: network,
518525
payload: Payload::WitnessProgram {
519526
version: WitnessVersion::V1,
520-
program: taptweaked_key.serialize().to_vec()
527+
program: internal_key.tap_tweak(secp, merkle_root).into_inner().serialize().to_vec()
528+
}
529+
}
530+
}
531+
532+
/// Create a pay to taproot address from a pre-tweaked output key.
533+
///
534+
/// This method is not recommended for use and [Address::p2tr()] should be used where possible.
535+
pub fn p2tr_tweaked<C: Verification>(
536+
output_key: TweakedPublicKey,
537+
network: Network
538+
) -> Address {
539+
Address {
540+
network: network,
541+
payload: Payload::WitnessProgram {
542+
version: WitnessVersion::V1,
543+
program: output_key.into_inner().serialize().to_vec()
521544
}
522545
}
523546
}
@@ -786,6 +809,7 @@ mod tests {
786809
use blockdata::script::Script;
787810
use network::constants::Network::{Bitcoin, Testnet};
788811
use util::ecdsa::PublicKey;
812+
use secp256k1::schnorrsig;
789813

790814
use super::*;
791815

@@ -1148,4 +1172,12 @@ mod tests {
11481172
test_addr_type(legacy_payload, LEGACY_EQUIVALENCE_CLASSES);
11491173
test_addr_type(&segwit_payload, SEGWIT_EQUIVALENCE_CLASSES);
11501174
}
1175+
1176+
#[test]
1177+
fn p2tr_from_untweaked(){
1178+
//Test case from BIP-086
1179+
let internal_key = schnorrsig::PublicKey::from_str("cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115").unwrap();
1180+
let address = Address::p2tr(Secp256k1::new(), internal_key,None, Network::Bitcoin);
1181+
assert_eq!(address.to_string(), "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr");
1182+
}
11511183
}

src/util/schnorr.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,67 @@
1717
//!
1818
1919
pub use secp256k1::schnorrsig::{PublicKey, KeyPair};
20+
use secp256k1::{Secp256k1, Verification};
21+
use hashes::{Hash, HashEngine};
22+
use util::taproot::{TapBranchHash, TapTweakHash};
23+
24+
/// Untweaked Schnorr public key
25+
pub type UntweakedPublicKey = PublicKey;
26+
27+
/// Tweaked Schnorr public key
28+
pub struct TweakedPublicKey(PublicKey);
29+
30+
/// A trait for tweaking Schnorr public keys
31+
pub trait TapTweak {
32+
/// Tweaks an untweaked public key given an untweaked key and optional script tree merkle root.
33+
///
34+
/// This is done by using the equation Q = P + H(P|c)G, where
35+
/// * Q is the tweaked key
36+
/// * P is the internal key
37+
/// * H is the hash function
38+
/// * c is the commitment data
39+
/// * G is the generator point
40+
fn tap_tweak<C: Verification>(&self, secp: Secp256k1<C>, merkle_root: Option<TapBranchHash>) -> TweakedPublicKey;
41+
42+
/// Directly convert an UntweakedPublicKey to a TweakedPublicKey
43+
///
44+
/// This method is dangerous and can lead to loss of funds if used incorrectly.
45+
/// Specifically, in multi-party protocols a peer can provide a value that allows them to steal.
46+
fn dangerous_assume_tweaked(self) -> TweakedPublicKey;
47+
}
48+
49+
impl TapTweak for UntweakedPublicKey {
50+
fn tap_tweak<C: Verification>(&self, secp: Secp256k1<C>, merkle_root: Option<TapBranchHash>) -> TweakedPublicKey {
51+
// Compute the tweak
52+
let mut engine = TapTweakHash::engine();
53+
engine.input(&self.serialize());
54+
merkle_root.map(|hash| engine.input(&hash));
55+
let tweak_value: [u8; 32] = TapTweakHash::from_engine(engine).into_inner();
56+
57+
58+
//Tweak the internal key by the tweak value
59+
let mut output_key = self.clone();
60+
let parity = output_key.tweak_add_assign(&secp, &tweak_value).expect("Tap tweak failed");
61+
if self.tweak_add_check(&secp, &output_key, parity, tweak_value) {
62+
return TweakedPublicKey(output_key);
63+
} else { unreachable!("Tap tweak failed") }
64+
}
65+
66+
67+
fn dangerous_assume_tweaked(self) -> TweakedPublicKey {
68+
TweakedPublicKey(self)
69+
}
70+
}
71+
72+
73+
impl TweakedPublicKey {
74+
/// Create a new [TweakedPublicKey] from a [PublicKey]. No tweak is applied.
75+
pub fn new(key: PublicKey) -> TweakedPublicKey {
76+
TweakedPublicKey(key)
77+
}
78+
79+
/// Returns the underlying public key
80+
pub fn into_inner(self) -> PublicKey {
81+
self.0
82+
}
83+
}

0 commit comments

Comments
 (0)