breez_sdk_liquid/payjoin/pset/
blind.rs1use anyhow::{anyhow, Result};
2use bip39::rand;
3use lwk_wollet::bitcoin::secp256k1::SecretKey;
4use lwk_wollet::elements::pset::Input;
5use lwk_wollet::elements::secp256k1_zkp::Generator;
6use lwk_wollet::elements::{self, bitcoin, confidential, secp256k1_zkp};
7use lwk_wollet::elements::{
8 confidential::{AssetBlindingFactor, ValueBlindingFactor},
9 pset::{
10 raw::{ProprietaryKey, ProprietaryType},
11 PartiallySignedTransaction,
12 },
13 TxOutSecrets,
14};
15
16const PSET_IN_EXPLICIT_VALUE: ProprietaryType = 0x11; const PSET_IN_VALUE_PROOF: ProprietaryType = 0x12; const PSET_IN_EXPLICIT_ASSET: ProprietaryType = 0x13; const PSET_IN_ASSET_PROOF: ProprietaryType = 0x14; pub fn remove_explicit_values(pset: &mut PartiallySignedTransaction) {
22 for input in pset.inputs_mut() {
23 for subtype in [
24 PSET_IN_EXPLICIT_VALUE,
25 PSET_IN_EXPLICIT_ASSET,
26 PSET_IN_VALUE_PROOF,
27 PSET_IN_ASSET_PROOF,
28 ] {
29 input
30 .proprietary
31 .remove(&ProprietaryKey::from_pset_pair(subtype, Vec::new()));
32 }
33 }
34}
35
36fn add_input_explicit_proofs(input: &mut Input, secret: &TxOutSecrets) -> Result<()> {
37 if secret.asset_bf == AssetBlindingFactor::zero()
38 && secret.value_bf == ValueBlindingFactor::zero()
39 {
40 return Ok(());
41 }
42 let secp = secp256k1_zkp::global::SECP256K1;
43 let mut rng = rand::thread_rng();
44 let asset_gen_unblinded = Generator::new_unblinded(secp, secret.asset.into_tag());
45 let asset_gen_blinded = input
46 .witness_utxo
47 .as_ref()
48 .ok_or(anyhow!("No witness utxo"))?
49 .asset
50 .into_asset_gen(secp)
51 .ok_or(anyhow!("No asset gen"))?;
52
53 let blind_asset_proof = secp256k1_zkp::SurjectionProof::new(
54 secp,
55 &mut rng,
56 secret.asset.into_tag(),
57 secret.asset_bf.into_inner(),
58 &[(
59 asset_gen_unblinded,
60 secret.asset.into_tag(),
61 secp256k1_zkp::ZERO_TWEAK,
62 )],
63 )?;
64
65 let blind_value_proof = secp256k1_zkp::RangeProof::new(
66 secp,
67 secret.value,
68 input
69 .witness_utxo
70 .as_ref()
71 .ok_or(anyhow!("No witness utxo"))?
72 .value
73 .commitment()
74 .ok_or(anyhow!("Invalid commitment"))?,
75 secret.value,
76 secret.value_bf.into_inner(),
77 &[],
78 &[],
79 secp256k1_zkp::SecretKey::new(&mut rng),
80 -1,
81 0,
82 asset_gen_blinded,
83 )?;
84
85 input.proprietary.insert(
86 ProprietaryKey::from_pset_pair(PSET_IN_EXPLICIT_VALUE, Vec::new()),
87 elements::encode::serialize(&secret.value),
88 );
89
90 input.proprietary.insert(
91 ProprietaryKey::from_pset_pair(PSET_IN_EXPLICIT_ASSET, Vec::new()),
92 elements::encode::serialize(&secret.asset),
93 );
94
95 let mut blind_value_proof = elements::encode::serialize(&blind_value_proof);
96 blind_value_proof.remove(0);
97 let mut blind_asset_proof = elements::encode::serialize(&blind_asset_proof);
98 blind_asset_proof.remove(0);
99
100 input.proprietary.insert(
101 ProprietaryKey::from_pset_pair(PSET_IN_VALUE_PROOF, Vec::new()),
102 blind_value_proof,
103 );
104
105 input.proprietary.insert(
106 ProprietaryKey::from_pset_pair(PSET_IN_ASSET_PROOF, Vec::new()),
107 blind_asset_proof,
108 );
109
110 Ok(())
111}
112
113pub fn blind_pset(
114 pset: &mut PartiallySignedTransaction,
115 inp_txout_sec: &[TxOutSecrets],
116 blinding_factors: &[(AssetBlindingFactor, ValueBlindingFactor, SecretKey)],
117) -> Result<()> {
118 let secp = secp256k1_zkp::global::SECP256K1;
119 let rng = &mut rand::thread_rng();
120
121 for (input, secret) in pset.inputs_mut().iter_mut().zip(inp_txout_sec.iter()) {
122 add_input_explicit_proofs(input, secret)?;
123 }
124
125 let mut last_blinded_index = None;
126 let mut exp_out_secrets = Vec::new();
127
128 for (index, out) in pset.outputs().iter().enumerate() {
129 if out.blinding_key.is_none() {
130 let value = out
131 .amount
132 .ok_or(anyhow!("Output {index} value must be set"))?;
133 exp_out_secrets.push((
134 value,
135 AssetBlindingFactor::zero(),
136 ValueBlindingFactor::zero(),
137 ));
138 } else {
139 last_blinded_index = Some(index);
140 }
141 }
142
143 let last_blinded_index = last_blinded_index.ok_or(anyhow!("No blinding output found"))?;
144
145 let inputs = inp_txout_sec
146 .iter()
147 .map(|secret| {
148 let tag = secret.asset.into_tag();
149 let tweak = secret.asset_bf.into_inner();
150 let gen = Generator::new_blinded(secp, tag, tweak);
151 (gen, tag, tweak)
152 })
153 .collect::<Vec<_>>();
154
155 for (index, output) in pset.outputs_mut().iter_mut().enumerate() {
156 let asset_id = output
157 .asset
158 .ok_or(anyhow!("Output {index} asset must be set"))?;
159 let value = output
160 .amount
161 .ok_or(anyhow!("Output {index} value must be set"))?;
162 if let Some(receiver_blinding_pk) = output.blinding_key {
163 let is_last = index == last_blinded_index;
164 let blinding_factor = blinding_factors.get(index);
165
166 let out_abf = if let Some(blinding_factor) = blinding_factor {
167 blinding_factor.0
168 } else {
169 AssetBlindingFactor::new(rng)
170 };
171
172 let out_asset_commitment =
173 Generator::new_blinded(secp, asset_id.into_tag(), out_abf.into_inner());
174
175 let out_vbf = if is_last {
176 let inp_secrets = inp_txout_sec
177 .iter()
178 .map(|o| (o.value, o.asset_bf, o.value_bf))
179 .collect::<Vec<_>>();
180
181 ValueBlindingFactor::last(secp, value, out_abf, &inp_secrets, &exp_out_secrets)
182 } else if let Some(blinding_factor) = blinding_factor {
183 blinding_factor.1
184 } else {
185 ValueBlindingFactor::new(rng)
186 };
187
188 let value_commitment = secp256k1_zkp::PedersenCommitment::new(
189 secp,
190 value,
191 out_vbf.into_inner(),
192 out_asset_commitment,
193 );
194
195 let ephemeral_sk = if let Some(blinding_factor) = blinding_factor {
196 blinding_factor.2
197 } else {
198 SecretKey::new(rng)
199 };
200
201 let (nonce, shared_secret) = confidential::Nonce::with_ephemeral_sk(
202 secp,
203 ephemeral_sk,
204 &receiver_blinding_pk.inner,
205 );
206
207 let mut message = [0u8; 64];
208 message[..32].copy_from_slice(asset_id.into_tag().as_ref());
209 message[32..].copy_from_slice(out_abf.into_inner().as_ref());
210
211 let rangeproof = secp256k1_zkp::RangeProof::new(
212 secp,
213 1,
214 value_commitment,
215 value,
216 out_vbf.into_inner(),
217 &message,
218 output.script_pubkey.as_bytes(),
219 shared_secret,
220 0,
221 52,
222 out_asset_commitment,
223 )?;
224
225 let surjection_proof = secp256k1_zkp::SurjectionProof::new(
226 secp,
227 rng,
228 asset_id.into_tag(),
229 out_abf.into_inner(),
230 &inputs,
231 )?;
232
233 output.value_rangeproof = Some(Box::new(rangeproof));
234 output.asset_surjection_proof = Some(Box::new(surjection_proof));
235 output.amount_comm = Some(value_commitment);
236 output.asset_comm = Some(out_asset_commitment);
237 output.ecdh_pubkey = nonce.commitment().map(|pk| bitcoin::PublicKey {
238 inner: pk,
239 compressed: true,
240 });
241
242 let gen = Generator::new_unblinded(secp, asset_id.into_tag());
243 output.blind_asset_proof = Some(Box::new(secp256k1_zkp::SurjectionProof::new(
244 secp,
245 rng,
246 asset_id.into_tag(),
247 out_abf.into_inner(),
248 &[(gen, asset_id.into_tag(), secp256k1_zkp::ZERO_TWEAK)],
249 )?));
250
251 output.blind_value_proof = Some(Box::new(secp256k1_zkp::RangeProof::new(
252 secp,
253 value,
254 value_commitment,
255 value,
256 out_vbf.into_inner(),
257 &[],
258 &[],
259 secp256k1_zkp::SecretKey::new(rng),
260 -1,
261 0,
262 out_asset_commitment,
263 )?));
264
265 exp_out_secrets.push((value, out_abf, out_vbf));
266 }
267 }
268
269 Ok(())
270}