use bitcoin::hash_types::{WPubkeyHash, WScriptHash};
use bitcoin::hashes::Hash;
use bitcoin::network::constants::Network;
-use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, self};
+use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, self};
use bitcoin::secp256k1::schnorr::Signature;
use bitcoin::util::address::{Address, Payload, WitnessVersion};
use bitcoin::util::schnorr::TweakedPublicKey;
-use core::convert::TryFrom;
+use core::convert::{Infallible, TryFrom};
use core::time::Duration;
use crate::io;
use crate::ln::PaymentHash;
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
/// [`Refund`]: crate::offers::refund::Refund
/// [module-level documentation]: self
-pub struct InvoiceBuilder<'a> {
+pub struct InvoiceBuilder<'a, S: SigningPubkeyStrategy> {
invreq_bytes: &'a Vec<u8>,
invoice: InvoiceContents,
+ keys: Option<KeyPair>,
+ signing_pubkey_strategy: core::marker::PhantomData<S>,
}
-impl<'a> InvoiceBuilder<'a> {
+/// Indicates how [`Invoice::signing_pubkey`] was set.
+pub trait SigningPubkeyStrategy {}
+
+/// [`Invoice::signing_pubkey`] was explicitly set.
+pub struct ExplicitSigningPubkey {}
+
+/// [`Invoice::signing_pubkey`] was derived.
+pub struct DerivedSigningPubkey {}
+
+impl SigningPubkeyStrategy for ExplicitSigningPubkey {}
+impl SigningPubkeyStrategy for DerivedSigningPubkey {}
+
+impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
pub(super) fn for_offer(
invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>,
created_at: Duration, payment_hash: PaymentHash
) -> Result<Self, SemanticError> {
- let amount_msats = match invoice_request.amount_msats() {
- Some(amount_msats) => amount_msats,
- None => match invoice_request.contents.inner.offer.amount() {
- Some(Amount::Bitcoin { amount_msats }) => {
- amount_msats.checked_mul(invoice_request.quantity().unwrap_or(1))
- .ok_or(SemanticError::InvalidAmount)?
- },
- Some(Amount::Currency { .. }) => return Err(SemanticError::UnsupportedCurrency),
- None => return Err(SemanticError::MissingAmount),
- },
- };
-
+ let amount_msats = Self::check_amount_msats(invoice_request)?;
let contents = InvoiceContents::ForOffer {
invoice_request: invoice_request.contents.clone(),
fields: InvoiceFields {
},
};
- Self::new(&invoice_request.bytes, contents)
+ Self::new(&invoice_request.bytes, contents, None)
}
pub(super) fn for_refund(
},
};
- Self::new(&refund.bytes, contents)
+ Self::new(&refund.bytes, contents, None)
}
+}
- fn new(invreq_bytes: &'a Vec<u8>, contents: InvoiceContents) -> Result<Self, SemanticError> {
+impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
+ pub(super) fn for_offer_using_keys(
+ invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>,
+ created_at: Duration, payment_hash: PaymentHash, keys: KeyPair
+ ) -> Result<Self, SemanticError> {
+ let amount_msats = Self::check_amount_msats(invoice_request)?;
+ let contents = InvoiceContents::ForOffer {
+ invoice_request: invoice_request.contents.clone(),
+ fields: InvoiceFields {
+ payment_paths, created_at, relative_expiry: None, payment_hash, amount_msats,
+ fallbacks: None, features: Bolt12InvoiceFeatures::empty(),
+ signing_pubkey: invoice_request.contents.inner.offer.signing_pubkey(),
+ },
+ };
+
+ Self::new(&invoice_request.bytes, contents, Some(keys))
+ }
+}
+
+impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
+ fn check_amount_msats(invoice_request: &InvoiceRequest) -> Result<u64, SemanticError> {
+ match invoice_request.amount_msats() {
+ Some(amount_msats) => Ok(amount_msats),
+ None => match invoice_request.contents.inner.offer.amount() {
+ Some(Amount::Bitcoin { amount_msats }) => {
+ amount_msats.checked_mul(invoice_request.quantity().unwrap_or(1))
+ .ok_or(SemanticError::InvalidAmount)
+ },
+ Some(Amount::Currency { .. }) => Err(SemanticError::UnsupportedCurrency),
+ None => Err(SemanticError::MissingAmount),
+ },
+ }
+ }
+
+ fn new(
+ invreq_bytes: &'a Vec<u8>, contents: InvoiceContents, keys: Option<KeyPair>
+ ) -> Result<Self, SemanticError> {
if contents.fields().payment_paths.is_empty() {
return Err(SemanticError::MissingPaths);
}
- Ok(Self { invreq_bytes, invoice: contents })
+ Ok(Self {
+ invreq_bytes,
+ invoice: contents,
+ keys,
+ signing_pubkey_strategy: core::marker::PhantomData,
+ })
}
/// Sets the [`Invoice::relative_expiry`] as seconds since [`Invoice::created_at`]. Any expiry
self.invoice.fields_mut().features.set_basic_mpp_optional();
self
}
+}
+impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
/// Builds an unsigned [`Invoice`] after checking for valid semantics. It can be signed by
/// [`UnsignedInvoice::sign`].
pub fn build(self) -> Result<UnsignedInvoice<'a>, SemanticError> {
}
}
- let InvoiceBuilder { invreq_bytes, invoice } = self;
+ let InvoiceBuilder { invreq_bytes, invoice, .. } = self;
Ok(UnsignedInvoice { invreq_bytes, invoice })
}
}
+impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
+ /// Builds a signed [`Invoice`] after checking for valid semantics.
+ pub fn build_and_sign<T: secp256k1::Signing>(
+ self, secp_ctx: &Secp256k1<T>
+ ) -> Result<Invoice, SemanticError> {
+ #[cfg(feature = "std")] {
+ if self.invoice.is_offer_or_refund_expired() {
+ return Err(SemanticError::AlreadyExpired);
+ }
+ }
+
+ let InvoiceBuilder { invreq_bytes, invoice, keys, .. } = self;
+ let keys = match &invoice {
+ InvoiceContents::ForOffer { .. } => keys.unwrap(),
+ InvoiceContents::ForRefund { .. } => unreachable!(),
+ };
+
+ let unsigned_invoice = UnsignedInvoice { invreq_bytes, invoice };
+ let invoice = unsigned_invoice
+ .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
+ .unwrap();
+ Ok(invoice)
+ }
+}
+
/// A semantically valid [`Invoice`] that hasn't been signed.
pub struct UnsignedInvoice<'a> {
invreq_bytes: &'a Vec<u8>,
},
};
- signer::verify_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx)
+ match signer::verify_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx) {
+ Ok(_) => true,
+ Err(()) => false,
+ }
}
fn derives_keys(&self) -> bool {
use bitcoin::util::schnorr::TweakedPublicKey;
use core::convert::TryFrom;
use core::time::Duration;
- use crate::ln::msgs::DecodeError;
+ use crate::chain::keysinterface::KeyMaterial;
use crate::ln::features::Bolt12InvoiceFeatures;
+ use crate::ln::inbound_payment::ExpandedKey;
+ use crate::ln::msgs::DecodeError;
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef, Quantity};
use crate::offers::payer::PayerTlvStreamRef;
use crate::offers::refund::RefundBuilder;
use crate::offers::test_utils::*;
+ use crate::onion_message::{BlindedHop, BlindedPath};
use crate::util::ser::{BigSize, Iterable, Writeable};
trait ToBytes {
}
}
+ #[test]
+ fn builds_invoice_from_offer_using_derived_keys() {
+ let desc = "foo".to_string();
+ let node_id = recipient_pubkey();
+ let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
+ let entropy = FixedEntropy {};
+ let secp_ctx = Secp256k1::new();
+
+ let blinded_path = BlindedPath {
+ introduction_node_id: pubkey(40),
+ blinding_point: pubkey(41),
+ blinded_hops: vec![
+ BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] },
+ BlindedHop { blinded_node_id: node_id, encrypted_payload: vec![0; 44] },
+ ],
+ };
+
+ let offer = OfferBuilder
+ ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
+ .amount_msats(1000)
+ .path(blinded_path)
+ .build().unwrap();
+ let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap();
+
+ if let Err(e) = invoice_request
+ .verify_and_respond_using_derived_keys_no_std(
+ payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
+ )
+ .unwrap()
+ .build_and_sign(&secp_ctx)
+ {
+ panic!("error building invoice: {:?}", e);
+ }
+
+ let expanded_key = ExpandedKey::new(&KeyMaterial([41; 32]));
+ match invoice_request.verify_and_respond_using_derived_keys_no_std(
+ payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
+ ) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, SemanticError::InvalidMetadata),
+ }
+
+ let desc = "foo".to_string();
+ let offer = OfferBuilder
+ ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
+ .amount_msats(1000)
+ .build().unwrap();
+ let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+ .build().unwrap()
+ .sign(payer_sign).unwrap();
+
+ match invoice_request.verify_and_respond_using_derived_keys_no_std(
+ payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
+ ) {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, SemanticError::InvalidMetadata),
+ }
+ }
+
#[test]
fn builds_invoice_with_relative_expiry() {
let now = now();
use crate::ln::features::InvoiceRequestFeatures;
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
use crate::ln::msgs::DecodeError;
-use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder};
-use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TlvStream, self};
+use crate::offers::invoice::{BlindedPayInfo, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder};
+use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, self};
use crate::offers::offer::{Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef};
use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
#[cfg(feature = "std")]
pub fn respond_with(
&self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash
- ) -> Result<InvoiceBuilder, SemanticError> {
+ ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
let created_at = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
pub fn respond_with_no_std(
&self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
created_at: core::time::Duration
- ) -> Result<InvoiceBuilder, SemanticError> {
+ ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
if self.features().requires_unknown_bits() {
return Err(SemanticError::UnknownRequiredFeatures);
}
InvoiceBuilder::for_offer(self, payment_paths, created_at, payment_hash)
}
- /// Verifies that the request was for an offer created using the given key.
+ /// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
+ /// derived signing keys from the originating [`Offer`] to sign the [`Invoice`]. Must use the
+ /// same [`ExpandedKey`] as the one used to create the offer.
+ ///
+ /// See [`InvoiceRequest::respond_with`] for further details.
+ ///
+ /// [`Invoice`]: crate::offers::invoice::Invoice
+ #[cfg(feature = "std")]
+ pub fn verify_and_respond_using_derived_keys<T: secp256k1::Signing>(
+ &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+ expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+ ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, SemanticError> {
+ let created_at = std::time::SystemTime::now()
+ .duration_since(std::time::SystemTime::UNIX_EPOCH)
+ .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
+
+ self.verify_and_respond_using_derived_keys_no_std(
+ payment_paths, payment_hash, created_at, expanded_key, secp_ctx
+ )
+ }
+
+ /// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses
+ /// derived signing keys from the originating [`Offer`] to sign the [`Invoice`]. Must use the
+ /// same [`ExpandedKey`] as the one used to create the offer.
+ ///
+ /// See [`InvoiceRequest::respond_with_no_std`] for further details.
+ ///
+ /// [`Invoice`]: crate::offers::invoice::Invoice
+ pub fn verify_and_respond_using_derived_keys_no_std<T: secp256k1::Signing>(
+ &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+ created_at: core::time::Duration, expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+ ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, SemanticError> {
+ if self.features().requires_unknown_bits() {
+ return Err(SemanticError::UnknownRequiredFeatures);
+ }
+
+ let keys = match self.verify(expanded_key, secp_ctx) {
+ Err(()) => return Err(SemanticError::InvalidMetadata),
+ Ok(None) => return Err(SemanticError::InvalidMetadata),
+ Ok(Some(keys)) => keys,
+ };
+
+ InvoiceBuilder::for_offer_using_keys(self, payment_paths, created_at, payment_hash, keys)
+ }
+
+ /// Verifies that the request was for an offer created using the given key. Returns the derived
+ /// keys need to sign an [`Invoice`] for the request if they could be extracted from the
+ /// metadata.
+ ///
+ /// [`Invoice`]: crate::offers::invoice::Invoice
pub fn verify<T: secp256k1::Signing>(
&self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
- ) -> bool {
- self.contents.inner.offer.verify(TlvStream::new(&self.bytes), key, secp_ctx)
+ ) -> Result<Option<KeyPair>, ()> {
+ self.contents.inner.offer.verify(&self.bytes, key, secp_ctx)
}
#[cfg(test)]
use bitcoin::blockdata::constants::ChainHash;
use bitcoin::network::constants::Network;
-use bitcoin::secp256k1::{PublicKey, Secp256k1, self};
+use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self};
use core::convert::TryFrom;
use core::num::NonZeroU64;
use core::ops::Deref;
#[cfg(feature = "std")]
use std::time::SystemTime;
-const IV_BYTES: &[u8; IV_LEN] = b"LDK Offer ~~~~~~";
+pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Offer ~~~~~~";
/// Builds an [`Offer`] for the "offer to be paid" flow.
///
/// Verifies that the offer metadata was produced from the offer in the TLV stream.
pub(super) fn verify<T: secp256k1::Signing>(
- &self, tlv_stream: TlvStream<'_>, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
- ) -> bool {
+ &self, bytes: &[u8], key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+ ) -> Result<Option<KeyPair>, ()> {
match self.metadata() {
Some(metadata) => {
- let tlv_stream = tlv_stream.range(OFFER_TYPES).filter(|record| {
+ let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES).filter(|record| {
match record.r#type {
OFFER_METADATA_TYPE => false,
OFFER_NODE_ID_TYPE => !self.metadata.as_ref().unwrap().derives_keys(),
metadata, key, IV_BYTES, self.signing_pubkey(), tlv_stream, secp_ctx
)
},
- None => false,
+ None => Err(()),
}
}
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
- assert!(invoice_request.verify(&expanded_key, &secp_ctx));
+ assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_ok());
// Fails verification with altered offer field
let mut tlv_stream = offer.as_tlv_stream();
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
- assert!(!invoice_request.verify(&expanded_key, &secp_ctx));
+ assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
// Fails verification with altered metadata
let mut tlv_stream = offer.as_tlv_stream();
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
- assert!(!invoice_request.verify(&expanded_key, &secp_ctx));
+ assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
}
#[test]
let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
- assert!(invoice_request.verify(&expanded_key, &secp_ctx));
+ assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_ok());
// Fails verification with altered offer field
let mut tlv_stream = offer.as_tlv_stream();
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
- assert!(!invoice_request.verify(&expanded_key, &secp_ctx));
+ assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
// Fails verification with altered signing pubkey
let mut tlv_stream = offer.as_tlv_stream();
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
.build().unwrap()
.sign(payer_sign).unwrap();
- assert!(!invoice_request.verify(&expanded_key, &secp_ctx));
+ assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
}
#[test]
InvalidQuantity,
/// A quantity or quantity bounds was provided but was not expected.
UnexpectedQuantity,
+ /// Metadata could not be used to verify the offers message.
+ InvalidMetadata,
/// Metadata was provided but was not expected.
UnexpectedMetadata,
/// Payer metadata was expected but was missing.
use crate::ln::features::InvoiceRequestFeatures;
use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
-use crate::offers::invoice::{BlindedPayInfo, InvoiceBuilder};
+use crate::offers::invoice::{BlindedPayInfo, ExplicitSigningPubkey, InvoiceBuilder};
use crate::offers::invoice_request::{InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef};
use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
pub fn respond_with(
&self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
signing_pubkey: PublicKey,
- ) -> Result<InvoiceBuilder, SemanticError> {
+ ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
let created_at = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
pub fn respond_with_no_std(
&self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
signing_pubkey: PublicKey, created_at: Duration
- ) -> Result<InvoiceBuilder, SemanticError> {
+ ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
if self.features().requires_unknown_bits() {
return Err(SemanticError::UnknownRequiredFeatures);
}
metadata: &[u8], expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
signing_pubkey: PublicKey, tlv_stream: impl core::iter::Iterator<Item = TlvRecord<'a>>,
secp_ctx: &Secp256k1<T>
-) -> bool {
+) -> Result<Option<KeyPair>, ()> {
+ let hmac = hmac_for_message(metadata, expanded_key, iv_bytes, tlv_stream)?;
+
+ if metadata.len() == Nonce::LENGTH {
+ let derived_keys = KeyPair::from_secret_key(
+ secp_ctx, &SecretKey::from_slice(hmac.as_inner()).unwrap()
+ );
+ if fixed_time_eq(&signing_pubkey.serialize(), &derived_keys.public_key().serialize()) {
+ Ok(Some(derived_keys))
+ } else {
+ Err(())
+ }
+ } else if metadata[Nonce::LENGTH..].len() == Sha256::LEN {
+ if fixed_time_eq(&metadata[Nonce::LENGTH..], &hmac.into_inner()) {
+ Ok(None)
+ } else {
+ Err(())
+ }
+ } else {
+ Err(())
+ }
+}
+
+fn hmac_for_message<'a>(
+ metadata: &[u8], expanded_key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
+ tlv_stream: impl core::iter::Iterator<Item = TlvRecord<'a>>
+) -> Result<Hmac<Sha256>, ()> {
if metadata.len() < Nonce::LENGTH {
- return false;
+ return Err(());
}
let nonce = match Nonce::try_from(&metadata[..Nonce::LENGTH]) {
Ok(nonce) => nonce,
- Err(_) => return false,
+ Err(_) => return Err(()),
};
let mut hmac = expanded_key.hmac_for_offer(nonce, iv_bytes);
if metadata.len() == Nonce::LENGTH {
hmac.input(DERIVED_METADATA_AND_KEYS_HMAC_INPUT);
- let hmac = Hmac::from_engine(hmac);
- let derived_pubkey = SecretKey::from_slice(hmac.as_inner()).unwrap().public_key(&secp_ctx);
- fixed_time_eq(&signing_pubkey.serialize(), &derived_pubkey.serialize())
- } else if metadata[Nonce::LENGTH..].len() == Sha256::LEN {
- hmac.input(DERIVED_METADATA_HMAC_INPUT);
- fixed_time_eq(&metadata[Nonce::LENGTH..], &Hmac::from_engine(hmac).into_inner())
} else {
- false
+ hmac.input(DERIVED_METADATA_HMAC_INPUT);
}
+
+ Ok(Hmac::from_engine(hmac))
}