Skip to content

Commit daf9d83

Browse files
committed
Merge #289: Improve types on AssetIssuance struct
4d39dbf issuance: add Encode/Decode impl for AssetID (Andrew Poelstra) 653d3aa issuance: introduce AssetBlindingNonce newtype for issuance blinding nonce (Andrew Poelstra) 1d4934b issuance: implement Encode and Decode for AssetEntropy (Andrew Poelstra) f3094b5 transaction: use AssetEntropy in AssetIssuance entropy field (Andrew Poelstra) Pull request description: Use strong types for asset entropy and blinding nonce. ACKs for top commit: delta1: ACK 4d39dbf; tested locally Tree-SHA512: ce3758cb684f937497e926a409d197347d50175df1fae9286bd62e4147199b0d194fc2c55acb90c09a7e2531a22c5f669449dd4ede6e452607ef689274266ee6
2 parents 39a759a + 4d39dbf commit daf9d83

10 files changed

Lines changed: 258 additions & 102 deletions

File tree

src/genesis.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@
1515
//! Helpers to calculate the genesis block for a given network.
1616
1717
use bitcoin::secp256k1::impl_array_newtype;
18-
use secp256k1_zkp::Tweak;
1918
use crate::hashes::{sha256, HashEngine};
2019
use crate::opcodes::all::OP_RETURN;
2120
use crate::opcodes::OP_TRUE;
2221
use crate::{confidential, script, AssetId, Block, BlockExtData, BlockHash, BlockHeader, LockTime, Script, Sequence, Transaction, TxIn, TxInWitness, TxOut, TxOutWitness};
23-
use crate::{AssetIssuance, ContractHash, OutPoint, Txid};
22+
use crate::{AssetBlindingNonce, AssetEntropy, AssetIssuance, ContractHash, OutPoint, Txid};
2423
use crate::confidential::Nonce;
2524

2625
/// Parameters that influence chain consensus. The contents of the genesis block for a given network
@@ -146,8 +145,8 @@ fn liquid_genesis_asset_tx(network_params: &NetworkParams) -> Option<Transaction
146145
let asset_id = AssetId::from_entropy(asset_entropy);
147146

148147
let asset_issuance = AssetIssuance {
149-
asset_blinding_nonce: Tweak::default(),
150-
asset_entropy: [0u8; 32],
148+
asset_blinding_nonce: AssetBlindingNonce::NEW_ISSUANCE,
149+
asset_entropy: AssetEntropy::NEW_ISSUANCE,
151150
amount: confidential::Value::Explicit(asset_amount),
152151
inflation_keys: confidential::Value::Explicit(0),
153152
};

src/internal_macros.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -406,22 +406,22 @@ macro_rules! impl_sha256_midstate_wrapper {
406406

407407
impl $ty {
408408
/// Constructs this wrapper struct from raw bytes.
409-
pub fn from_byte_array(inner: [u8; 32]) -> Self {
409+
pub const fn from_byte_array(inner: [u8; 32]) -> Self {
410410
Self(inner)
411411
}
412412

413413
/// The raw bytes within the wrapper type.
414-
pub fn as_byte_array(&self) -> &[u8; 32] {
414+
pub const fn as_byte_array(&self) -> &[u8; 32] {
415415
&self.0
416416
}
417417

418418
/// The raw bytes within the wrapper type.
419-
pub fn to_byte_array(self) -> [u8; 32] {
419+
pub const fn to_byte_array(self) -> [u8; 32] {
420420
self.0
421421
}
422422

423423
/// (Private) convert a sha256 midstate to an object.
424-
fn from_midstate(value: crate::hashes::sha256::Midstate) -> Self {
424+
const fn from_midstate(value: crate::hashes::sha256::Midstate) -> Self {
425425
Self(value.to_parts().0)
426426
}
427427
}

src/issuance.rs

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414

1515
//! Asset Issuance
1616
17+
use core::fmt;
1718
use std::io;
1819

20+
use crate::confidential::AssetBlindingFactor;
1921
use crate::encode::{self, Encodable, Decodable};
2022
use crate::hashes::{hash_newtype, sha256, sha256d};
2123
use crate::fast_merkle_root::fast_merkle_root;
@@ -51,6 +53,158 @@ impl_sha256_midstate_wrapper! {
5153
pub struct AssetEntropy([u8; 32]);
5254
}
5355

56+
impl AssetEntropy {
57+
/// The all-zeroes "entropy" used for new issuances (vs reissuances).
58+
pub const NEW_ISSUANCE: Self = Self([0; 32]);
59+
60+
/// Re-interpret the asset entropy as a contract hash.
61+
pub fn into_contract_hash(self) -> ContractHash {
62+
ContractHash::from_byte_array(self.0)
63+
}
64+
}
65+
66+
impl Encodable for AssetEntropy {
67+
fn consensus_encode<W: io::Write>(&self, e: W) -> Result<usize, encode::Error> {
68+
self.0.consensus_encode(e)
69+
}
70+
}
71+
72+
impl Decodable for AssetEntropy {
73+
fn consensus_decode<D: io::Read>(d: D) -> Result<Self, encode::Error> {
74+
<[u8; 32]>::consensus_decode(d).map(Self)
75+
}
76+
}
77+
78+
encoding::encoder_newtype_exact! {
79+
/// Encoder for the [`AssetEntropyEncoder`] type.
80+
#[derive(Clone, Debug)]
81+
pub struct AssetEntropyEncoder<'e>(encoding::ArrayRefEncoder<'e, 32>);
82+
}
83+
84+
impl encoding::Encode for AssetEntropy {
85+
type Encoder<'e> = AssetEntropyEncoder<'e>;
86+
87+
fn encoder(&self) -> Self::Encoder<'_> {
88+
AssetEntropyEncoder::new(encoding::ArrayRefEncoder::without_length_prefix(&self.0))
89+
}
90+
}
91+
92+
decoder_newtype! {
93+
/// Decoder for the [`AssetEntropy`] type.
94+
#[derive(Default)]
95+
pub struct AssetEntropyDecoder(encoding::ArrayDecoder<32>);
96+
97+
/// Decoder error for the [`AssetEntropy`] type.
98+
#[derive(Clone, PartialEq, Eq, Debug)]
99+
pub struct AssetEntropyDecoderError(encoding::UnexpectedEofError);
100+
const ERROR_DISPLAY = "error decoding asset entropy";
101+
102+
impl Decode for AssetEntropy {
103+
fn convert_inner(bytes) -> Result<_, UnexpectedEofError> {
104+
Ok(AssetEntropy::from_byte_array(bytes))
105+
}
106+
}
107+
}
108+
109+
/// The blinding factor used to derive an asset commitment from an asset.
110+
///
111+
/// This type represents either [`Self::NEW_ISSUANCE`], indicating that an asset
112+
/// issuance is of a new asset, or for a reissuance, the [`AssetBlindingFactor`]
113+
/// used to blind the reissuance token (which must be blinded in order to be
114+
/// spent, due to a quirk in the Elements consensus code.)
115+
///
116+
/// Conceptually this can be thought of as an `Option<AssetBlindingFactor>`, except
117+
/// that there are no invalid values; [`AssetBlindingNonce::from_byte_array`] will
118+
/// always succeed. However, if an out-of-range value is used, the transaction will
119+
/// fail validation no matter what reissuance token is used.
120+
///
121+
/// Also, **unlike [`AssetBlindingFactor`], this type represents public data**. You
122+
/// can convert a blinding factor into a "blinding nonce", and while this conversion
123+
/// is technically a no-op, conceptually it represents choosing to make the blinding
124+
/// factor public.
125+
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Default)]
126+
pub struct AssetBlindingNonce([u8; 32]);
127+
128+
impl AssetBlindingNonce {
129+
/// A null blinding nonce, representing a new issuance (vs a reissuance).
130+
pub const NEW_ISSUANCE: Self = Self([0; 32]);
131+
132+
/// Constructs this wrapper struct from raw bytes.
133+
pub const fn from_byte_array(inner: [u8; 32]) -> Self {
134+
Self(inner)
135+
}
136+
137+
/// The raw bytes within the wrapper type.
138+
pub const fn as_byte_array(&self) -> &[u8; 32] {
139+
&self.0
140+
}
141+
142+
/// The raw bytes within the wrapper type.
143+
pub const fn to_byte_array(self) -> [u8; 32] {
144+
self.0
145+
}
146+
147+
/// Whether this is the null "new issuance" blinding nonce.
148+
pub fn is_null(&self) -> bool {
149+
// This is surprisingly annoying to make into a constfn, so we don't
150+
// bother for now.
151+
*self == Self::NEW_ISSUANCE
152+
}
153+
154+
/// Reinterpret an asset blinding factor as a [`AssetBlindingNonce`].
155+
///
156+
/// This is something of a dangerous function, since in general blinding factors should
157+
/// be considered secret data, while blinding nonces are public (they are encoded on
158+
/// the blockchain). So callers of this function should be sure that this is a blinding
159+
/// factor that they intend to reveal.)
160+
pub fn from_blinding_factor(bf: AssetBlindingFactor) -> Self {
161+
Self(*bf.into_inner().as_ref())
162+
}
163+
}
164+
165+
impl Encodable for AssetBlindingNonce {
166+
fn consensus_encode<W: io::Write>(&self, e: W) -> Result<usize, encode::Error> {
167+
self.0.consensus_encode(e)
168+
}
169+
}
170+
171+
impl Decodable for AssetBlindingNonce {
172+
fn consensus_decode<D: io::Read>(d: D) -> Result<Self, encode::Error> {
173+
<[u8; 32]>::consensus_decode(d).map(Self)
174+
}
175+
}
176+
177+
encoding::encoder_newtype_exact! {
178+
/// Encoder for the [`AssetBlindingNonce`] type.
179+
#[derive(Clone, Debug)]
180+
pub struct AssetBlindingNonceEncoder<'e>(encoding::ArrayRefEncoder<'e, 32>);
181+
}
182+
183+
impl encoding::Encode for AssetBlindingNonce {
184+
type Encoder<'e> = AssetBlindingNonceEncoder<'e>;
185+
186+
fn encoder(&self) -> Self::Encoder<'_> {
187+
AssetBlindingNonceEncoder::new(encoding::ArrayRefEncoder::without_length_prefix(&self.0))
188+
}
189+
}
190+
191+
decoder_newtype! {
192+
/// Decoder for the [`AssetBlindingNonce`] type.
193+
#[derive(Default)]
194+
pub struct AssetBlindingNonceDecoder(encoding::ArrayDecoder<32>);
195+
196+
/// Decoder error for the [`AssetBlindingNonce`] type.
197+
#[derive(Clone, PartialEq, Eq, Debug)]
198+
pub struct AssetBlindingNonceDecoderError(encoding::UnexpectedEofError);
199+
const ERROR_DISPLAY = "error decoding asset blinding nonce";
200+
201+
impl Decode for AssetBlindingNonce {
202+
fn convert_inner(bytes) -> Result<_, UnexpectedEofError> {
203+
Ok(AssetBlindingNonce::from_byte_array(bytes))
204+
}
205+
}
206+
}
207+
54208
impl_sha256_midstate_wrapper! {
55209
/// An issued asset ID.
56210
pub struct AssetId([u8; 32]);
@@ -189,6 +343,37 @@ impl Decodable for AssetId {
189343
}
190344
}
191345

346+
encoding::encoder_newtype_exact! {
347+
/// Encoder for the [`AssetId`] type.
348+
#[derive(Clone, Debug)]
349+
pub struct AssetIdEncoder<'e>(encoding::ArrayRefEncoder<'e, 32>);
350+
}
351+
352+
impl encoding::Encode for AssetId {
353+
type Encoder<'e> = AssetIdEncoder<'e>;
354+
355+
fn encoder(&self) -> Self::Encoder<'_> {
356+
AssetIdEncoder::new(encoding::ArrayRefEncoder::without_length_prefix(&self.0))
357+
}
358+
}
359+
360+
decoder_newtype! {
361+
/// Decoder for the [`AssetId`] type.
362+
#[derive(Default)]
363+
pub struct AssetIdDecoder(encoding::ArrayDecoder<32>);
364+
365+
/// Decoder error for the [`AssetId`] type.
366+
#[derive(Clone, PartialEq, Eq, Debug)]
367+
pub struct AssetIdDecoderError(encoding::UnexpectedEofError);
368+
const ERROR_DISPLAY = "error decoding asset ID";
369+
370+
impl Decode for AssetId {
371+
fn convert_inner(bytes) -> Result<_, UnexpectedEofError> {
372+
Ok(AssetId::from_byte_array(bytes))
373+
}
374+
}
375+
}
376+
192377
#[cfg(test)]
193378
mod test {
194379
use super::*;

src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,12 @@ pub use crate::confidential::{RangeProof, SurjectionProof};
8989
pub use crate::ext::{ReadExt, WriteExt};
9090
pub use crate::fast_merkle_root::fast_merkle_root;
9191
pub use crate::hash_types::*;
92-
pub use crate::issuance::{AssetEntropy, AssetId, ContractHash};
92+
pub use crate::issuance::{
93+
AssetBlindingNonce, AssetBlindingNonceDecoder, AssetBlindingNonceDecoderError,
94+
AssetBlindingNonceEncoder, AssetEntropy, AssetEntropyDecoder, AssetEntropyDecoderError,
95+
AssetEntropyEncoder, AssetId, AssetIdDecoder, AssetIdDecoderError, AssetIdEncoder,
96+
ContractHash,
97+
};
9398
pub use crate::locktime::LockTime;
9499
pub use crate::schnorr::{SchnorrSig, SchnorrSigError};
95100
pub use crate::script::Script;

src/pset/map/input.rs

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use std::{
2121
};
2222

2323
use crate::taproot::{ControlBlock, LeafVersion, TapNodeHash, TapLeafHash};
24-
use crate::{schnorr, AssetId, ContractHash};
24+
use crate::{schnorr, AssetId};
2525

2626
use crate::{confidential, locktime};
2727
use crate::encode::{self, Decodable};
@@ -32,10 +32,10 @@ use crate::pset::raw;
3232
use crate::pset::serialize;
3333
use crate::pset::{self, error, Error};
3434
use crate::{transaction::SighashTypeParseError, SchnorrSighashType};
35-
use crate::{AssetIssuance, BlockHash, EcdsaSighashType, PeginWitness, RangeProof, Script, Transaction, TxIn, TxOut, Txid, SurjectionProof};
35+
use crate::{AssetBlindingNonce, AssetIssuance, BlockHash, EcdsaSighashType, PeginWitness, RangeProof, Script, Transaction, TxIn, TxOut, Txid, SurjectionProof};
3636
use bitcoin::bip32::KeySource;
3737
use bitcoin::{PublicKey, key::XOnlyPublicKey};
38-
use secp256k1_zkp::{self, Tweak, ZERO_TWEAK};
38+
use secp256k1_zkp;
3939

4040
use crate::{OutPoint, Sequence};
4141

@@ -258,9 +258,9 @@ pub struct Input {
258258
/// Issuance inflation keys commitment
259259
pub issuance_inflation_keys_comm: Option<secp256k1_zkp::PedersenCommitment>,
260260
/// Issuance blinding nonce
261-
pub issuance_blinding_nonce: Option<Tweak>,
261+
pub issuance_blinding_nonce: Option<AssetBlindingNonce>,
262262
/// Issuance asset entropy
263-
pub issuance_asset_entropy: Option<[u8; 32]>,
263+
pub issuance_asset_entropy: Option<AssetEntropy>,
264264
/// input utxo rangeproof
265265
pub in_utxo_rangeproof: Option<RangeProof>,
266266
/// Proof that blinded issuance matches the commitment
@@ -524,19 +524,21 @@ impl Input {
524524
/// Compute the issuance asset ids from pset. This function does not check
525525
/// whether there is an issuance in this input. Returns (`asset_id`, `token_id`)
526526
pub fn issuance_ids(&self) -> (AssetId, AssetId) {
527-
let issue_nonce = self.issuance_blinding_nonce.unwrap_or(ZERO_TWEAK);
528-
let entropy = if issue_nonce == ZERO_TWEAK {
527+
let issue_nonce = self.issuance_blinding_nonce.unwrap_or_default();
528+
let entropy = if issue_nonce.is_null() {
529529
// new issuance
530530
let prevout = OutPoint {
531531
txid: self.previous_txid,
532532
vout: self.previous_output_index,
533533
};
534-
let contract_hash =
535-
ContractHash::from_byte_array(self.issuance_asset_entropy.unwrap_or_default());
534+
let contract_hash = self
535+
.issuance_asset_entropy
536+
.unwrap_or_default()
537+
.into_contract_hash();
536538
AssetId::generate_asset_entropy(prevout, contract_hash)
537539
} else {
538540
// re-issuance
539-
AssetEntropy::from_byte_array(self.issuance_asset_entropy.unwrap_or_default())
541+
self.issuance_asset_entropy.unwrap_or_default()
540542
};
541543
let asset_id = AssetId::from_entropy(entropy);
542544
let token_id =
@@ -558,7 +560,7 @@ impl Input {
558560
/// Get the issuance for this tx input
559561
pub fn asset_issuance(&self) -> AssetIssuance {
560562
AssetIssuance {
561-
asset_blinding_nonce: *self.issuance_blinding_nonce.as_ref().unwrap_or(&ZERO_TWEAK),
563+
asset_blinding_nonce: self.issuance_blinding_nonce.unwrap_or_default(),
562564
asset_entropy: self.issuance_asset_entropy.unwrap_or_default(),
563565
amount: match (self.issuance_value_amount, self.issuance_value_comm) {
564566
(None, None) => confidential::Value::Null,
@@ -752,10 +754,10 @@ impl Map for Input {
752754
impl_pset_prop_insert_pair!(self.issuance_inflation_keys_comm <= <raw_key: _> | <raw_value : secp256k1_zkp::PedersenCommitment>);
753755
}
754756
PSBT_ELEMENTS_IN_ISSUANCE_BLINDING_NONCE => {
755-
impl_pset_prop_insert_pair!(self.issuance_blinding_nonce <= <raw_key: _> | <raw_value : Tweak>);
757+
impl_pset_prop_insert_pair!(self.issuance_blinding_nonce <= <raw_key: _> | <raw_value : AssetBlindingNonce>);
756758
}
757759
PSBT_ELEMENTS_IN_ISSUANCE_ASSET_ENTROPY => {
758-
impl_pset_prop_insert_pair!(self.issuance_asset_entropy <= <raw_key: _> | <raw_value : [u8;32]>);
760+
impl_pset_prop_insert_pair!(self.issuance_asset_entropy <= <raw_key: _> | <raw_value : AssetEntropy>);
759761
}
760762
PSBT_ELEMENTS_IN_UTXO_RANGEPROOF => {
761763
impl_pset_prop_insert_pair!(self.in_utxo_rangeproof <= <raw_key: _> | <raw_value : RangeProof>);
@@ -1182,20 +1184,20 @@ where
11821184

11831185
#[cfg(test)]
11841186
mod tests {
1185-
use secp256k1_zkp::ZERO_TWEAK;
1186-
1187-
use crate::confidential;
1187+
use crate::{AssetBlindingNonce, confidential};
11881188
use crate::pset::PartiallySignedTransaction;
1189-
use crate::{AssetIssuance, LockTime, Transaction, TxIn, TxInWitness};
1189+
use crate::{AssetEntropy, AssetIssuance, LockTime, Transaction, TxIn, TxInWitness};
1190+
1191+
const DUMMY_ENTROPY: AssetEntropy = AssetEntropy::from_byte_array([1; 32]);
11901192

11911193
// See `pset::map::output::tests::from_tx_does_not_spuriously_set_proofs_on_unblinded_outputs`.
11921194
// Same principle, but for the asset issuance rangeproofs in the input witnesses.
11931195
#[test]
11941196
fn from_tx_does_not_spuriously_set_proofs_on_explicit_issuance() {
11951197
let txin = TxIn {
11961198
asset_issuance: AssetIssuance {
1197-
asset_blinding_nonce: ZERO_TWEAK,
1198-
asset_entropy: [1u8; 32],
1199+
asset_blinding_nonce: AssetBlindingNonce::NEW_ISSUANCE,
1200+
asset_entropy: DUMMY_ENTROPY,
11991201
amount: confidential::Value::Explicit(1000),
12001202
inflation_keys: confidential::Value::Null,
12011203
},

0 commit comments

Comments
 (0)