Skip to content

Commit

Permalink
epic: make anchor types generic over specific Dbc proof
Browse files Browse the repository at this point in the history
  • Loading branch information
dr-orlovsky committed Dec 14, 2023
1 parent 14175ef commit 38e0447
Show file tree
Hide file tree
Showing 12 changed files with 221 additions and 202 deletions.
128 changes: 27 additions & 101 deletions dbc/src/anchor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,20 @@
//! defined by LNPBP-4.
use std::cmp::Ordering;
use std::error::Error;

use amplify::{Bytes32, Wrapper};
use bc::{ScriptPubkey, Tx, Txid};
use bc::{Tx, Txid};
use commit_verify::mpc::{self, Message, ProtocolId};
use commit_verify::{CommitmentId, ConvolveCommitProof, ConvolveVerifyError};
use commit_verify::CommitmentId;
use strict_encoding::{StrictDumb, StrictEncode};

use crate::tapret::TapretProof;
use crate::LIB_NAME_BPCORE;

mod dbc {
pub use crate::Proof;
}

/// Default depth of LNPBP-4 commitment tree
pub const ANCHOR_MIN_LNPBP4_DEPTH: u8 = 3;

Expand Down Expand Up @@ -64,14 +68,14 @@ pub struct AnchorId(
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub enum VerifyError {
/// invalid deterministic bitcoin commitment. Details: {0}
#[from]
Dbc(AnchorError),
pub enum VerifyError<E: Error> {
/// Deterministic commitment error.
#[display(inner)]
Dbc(E),

/// LNPBP-4 invalid proof. Details: {0}
/// invalid MPC proof. Details: {0}
#[from]
Lnpbp4InvalidProof(mpc::InvalidProof),
Mpc(mpc::InvalidProof),
}

/// Anchor is a data structure used in deterministic bitcoin commitments for
Expand All @@ -87,27 +91,27 @@ pub enum VerifyError {
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct Anchor<L: mpc::Proof + StrictDumb> {
pub struct Anchor<L: mpc::Proof + StrictDumb, D: dbc::Proof> {
/// Transaction containing deterministic bitcoin commitment.
pub txid: Txid,

/// Structured multi-protocol LNPBP-4 data the transaction commits to.
pub mpc_proof: L,

/// Proof of the DBC commitment.
pub dbc_proof: Proof,
pub dbc_proof: D,
}

impl CommitmentId for Anchor<mpc::MerkleBlock> {
impl<D: dbc::Proof> CommitmentId for Anchor<mpc::MerkleBlock, D> {
const TAG: [u8; 32] = *b"urn:lnpbp:lnpbp0011:anchor:v01#A";
type Id = AnchorId;
}

impl Ord for Anchor<mpc::MerkleBlock> {
impl<D: dbc::Proof> Ord for Anchor<mpc::MerkleBlock, D> {
fn cmp(&self, other: &Self) -> Ordering { self.anchor_id().cmp(&other.anchor_id()) }
}

impl PartialOrd for Anchor<mpc::MerkleBlock> {
impl<D: dbc::Proof> PartialOrd for Anchor<mpc::MerkleBlock, D> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}

Expand All @@ -127,13 +131,13 @@ pub enum MergeError {
ProofMismatch,
}

impl Anchor<mpc::MerkleBlock> {
impl<D: dbc::Proof> Anchor<mpc::MerkleBlock, D> {
/// Returns id of the anchor (commitment hash).
#[inline]
pub fn anchor_id(&self) -> AnchorId { self.commitment_id() }
}

impl Anchor<mpc::MerkleProof> {
impl<D: dbc::Proof> Anchor<mpc::MerkleProof, D> {
/// Returns id of the anchor (commitment hash).
#[inline]
pub fn anchor_id(
Expand All @@ -149,7 +153,7 @@ impl Anchor<mpc::MerkleProof> {
self,
protocol_id: impl Into<ProtocolId>,
message: Message,
) -> Result<Anchor<mpc::MerkleBlock>, mpc::InvalidProof> {
) -> Result<Anchor<mpc::MerkleBlock, D>, mpc::InvalidProof> {
let lnpbp4_proof = mpc::MerkleBlock::with(&self.mpc_proof, protocol_id.into(), message)?;
Ok(Anchor {
txid: self.txid,
Expand All @@ -163,7 +167,7 @@ impl Anchor<mpc::MerkleProof> {
&self,
protocol_id: impl Into<ProtocolId>,
message: Message,
) -> Result<Anchor<mpc::MerkleBlock>, mpc::InvalidProof> {
) -> Result<Anchor<mpc::MerkleBlock, D>, mpc::InvalidProof> {
self.clone().into_merkle_block(protocol_id, message)
}

Expand All @@ -174,10 +178,10 @@ impl Anchor<mpc::MerkleProof> {
protocol_id: impl Into<ProtocolId>,
message: Message,
tx: &Tx,
) -> Result<(), VerifyError> {
) -> Result<(), VerifyError<D::Error>> {
self.dbc_proof
.verify(&self.mpc_proof.convolve(protocol_id.into(), message)?, tx)
.map_err(VerifyError::from)
.map_err(VerifyError::Dbc)
}

/// Verifies that the anchor commits to the given message under the given
Expand All @@ -191,13 +195,13 @@ impl Anchor<mpc::MerkleProof> {
}
}

impl Anchor<mpc::MerkleBlock> {
impl<D: dbc::Proof> Anchor<mpc::MerkleBlock, D> {
/// Conceals all LNPBP-4 data except specific protocol and produces merkle
/// proof anchor.
pub fn to_merkle_proof(
&self,
protocol: impl Into<ProtocolId>,
) -> Result<Anchor<mpc::MerkleProof>, mpc::LeafNotKnown> {
) -> Result<Anchor<mpc::MerkleProof, D>, mpc::LeafNotKnown> {
self.clone().into_merkle_proof(protocol)
}

Expand All @@ -206,7 +210,7 @@ impl Anchor<mpc::MerkleBlock> {
pub fn into_merkle_proof(
self,
protocol: impl Into<ProtocolId>,
) -> Result<Anchor<mpc::MerkleProof>, mpc::LeafNotKnown> {
) -> Result<Anchor<mpc::MerkleProof, D>, mpc::LeafNotKnown> {
let lnpbp4_proof = self.mpc_proof.to_merkle_proof(protocol.into())?;
Ok(Anchor {
txid: self.txid,
Expand Down Expand Up @@ -235,81 +239,3 @@ impl Anchor<mpc::MerkleBlock> {
Ok(self)
}
}

/// Errors covering failed anchor validation.
#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
#[display(doc_comments)]
pub enum AnchorError {
/// witness transaction {txid} contains invalid OP_RETURN commitment
/// {present:x} instead of {expected:x}.
OpretMismatch {
/// Transaction id
txid: Txid,
/// Commitment from the first OP_RETURN transaction output
present: ScriptPubkey,
/// Expected commitment absent in the first OP_RETURN transaction output
expected: ScriptPubkey,
},

/// witness transaction {0} does not contain any OP_RETURN commitment
/// required by the seal definition.
OpretAbsent(Txid),

#[from]
/// witness transaction does not contain a valid tapret commitment. {0}.
Tapret(ConvolveVerifyError),
}

/// Type and type-specific proof information of a deterministic bitcoin
/// commitment.
#[derive(Clone, PartialEq, Eq, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_BPCORE, tags = order)]
#[derive(CommitEncode)]
#[commit_encode(strategy = strict)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub enum Proof {
/// Opret commitment (no extra-transaction proof is required).
#[strict_type(dumb)]
OpretFirst,

/// Tapret commitment and a proof of it.
TapretFirst(TapretProof),
}

impl Proof {
/// Verifies validity of the proof.
pub fn verify(&self, msg: &mpc::Commitment, tx: &Tx) -> Result<(), AnchorError> {
match self {
Proof::OpretFirst => {
for txout in &tx.outputs {
if txout.script_pubkey.is_op_return() {
let expected = ScriptPubkey::op_return(msg.as_slice());
if txout.script_pubkey == expected {
return Ok(());
} else {
return Err(AnchorError::OpretMismatch {
txid: tx.txid(),
present: txout.script_pubkey.clone(),
expected,
});
}
}
}
Err(AnchorError::OpretAbsent(tx.txid()))
}
Proof::TapretFirst(proof) => {
ConvolveCommitProof::<_, Tx, _>::verify(proof, msg, tx).map_err(AnchorError::from)
}
}
}
}
4 changes: 3 additions & 1 deletion dbc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,7 @@ pub mod keytweak;
pub mod opret;
pub mod sigtweak;
pub mod tapret;
mod proof;

pub use anchor::{Anchor, AnchorId, Proof};
pub use anchor::{Anchor, AnchorId};
pub use proof::Proof;
74 changes: 66 additions & 8 deletions dbc/src/opret/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,69 @@
// limitations under the License.

//! ScriptPubkey-based OP_RETURN commitments.
//!
//! **Commit:**
//! a) `Msg -> ScriptPubkey`;
//! b) `Msg -> TxOut`;
//! c) `Msg -> (psbt::Output, TxOut)`;
//! **Convolve-commit:**
//! d) `Tx, Amount, Msg -> Tx'`;
//! e) `Psbt, Amount, Msg -> Psbt'`.
use bc::{ScriptPubkey, Tx, Txid};
use commit_verify::mpc::Commitment;

use crate::{Proof, LIB_NAME_BPCORE};

/// Errors covering failed anchor validation.
#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
#[display(doc_comments)]
pub enum OpretVerifyError {
/// witness transaction {txid} contains invalid OP_RETURN commitment
/// {present:x} instead of {expected:x}.
OpretMismatch {
/// Transaction id
txid: Txid,
/// Commitment from the first OP_RETURN transaction output
present: ScriptPubkey,
/// Expected commitment absent in the first OP_RETURN transaction output
expected: ScriptPubkey,
},

/// witness transaction {0} does not contain any OP_RETURN commitment
/// required by the seal definition.
OpretAbsent(Txid),
}

/// Empty type for use inside [`crate::Anchor`] for opret commitment scheme.
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_BPCORE)]
#[derive(CommitEncode)]
#[commit_encode(strategy = strict)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct OpretProof(());

impl Proof for OpretProof {
type Error = OpretVerifyError;

fn verify(&self, msg: &Commitment, tx: &Tx) -> Result<(), OpretVerifyError> {
// TODO: Use embed-commit-verify
for txout in &tx.outputs {
if txout.script_pubkey.is_op_return() {
let expected = ScriptPubkey::op_return(msg.as_slice());
if txout.script_pubkey == expected {
return Ok(());
} else {
return Err(OpretVerifyError::OpretMismatch {
txid: tx.txid(),
present: txout.script_pubkey.clone(),
expected,
});
}
}
}
Err(OpretVerifyError::OpretAbsent(tx.txid()))
}
}
36 changes: 13 additions & 23 deletions seals/src/txout/proto.rs → dbc/src/proof.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Bitcoin protocol single-use-seals library.
// Deterministic bitcoin commitments library.
//
// SPDX-License-Identifier: Apache-2.0
//
Expand All @@ -19,30 +19,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::convert::Infallible;
use std::error::Error;
use std::fmt::Debug;

use bc::Txid;
use commit_verify::mpc;
use single_use_seals::{SealProtocol, SealStatus};
use bc::Tx;
use commit_verify::{mpc, CommitEncode};
use strict_encoding::{StrictDecode, StrictDumb, StrictEncode};

use crate::resolver::Resolver;
use crate::txout::{TxoSeal, Witness};

/// Txo single-use-seal engine.
pub struct TxoProtocol<R: Resolver> {
#[allow(dead_code)]
resolver: R,
}

impl<Seal, R> SealProtocol<Seal> for TxoProtocol<R>
where
Seal: TxoSeal,
R: Resolver,
/// Deterministic bitcoin commitment proof types.
pub trait Proof:
Clone + Eq + CommitEncode + Debug + StrictEncode + StrictDecode + StrictDumb
{
type Witness = Witness;
type Message = mpc::Commitment;
type PublicationId = Txid;
type Error = Infallible;
/// Verification error.
type Error: Error;

fn get_seal_status(&self, _seal: &Seal) -> Result<SealStatus, Self::Error> { todo!() }
/// Verifies DBC proof against the provided transaction.
fn verify(&self, msg: &mpc::Commitment, tx: &Tx) -> Result<(), Self::Error>;
}
Loading

0 comments on commit 38e0447

Please sign in to comment.