Skip to content

Commit

Permalink
Merge pull request #22 from RGB-WG/consignments
Browse files Browse the repository at this point in the history
Creating and accepting transfers; working with PSBT
  • Loading branch information
dr-orlovsky authored Mar 22, 2023
2 parents d40345e + aaab4ee commit 675ac2a
Show file tree
Hide file tree
Showing 25 changed files with 2,068 additions and 546 deletions.
261 changes: 189 additions & 72 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,14 @@ name = "rgbwallet"
crate-type = ["cdylib", "rlib"] # We need this for WASM

[dependencies]
amplify = { workspace = true }
commit_verify = { workspace = true }
strict_encoding = { workspace = true }
bp-core = { workspace = true }
rgb-core = { workspace = true }
rgb-std = { version = "0.10.0-alpha.1", path = "std" }
# TODO: This dependencies should be replaced with psbt package
bitcoin = "0.30.0"

[features]
default = []
Expand All @@ -65,4 +71,7 @@ wasm-bindgen-test = "0.3"
features = [ "all" ]

[patch.crates-io]
commit_verify = { git = "https://github.com/LNP-BP/client_side_validation" }
bp-core = { git = "https://github.com/BP-WG/bp-core" }
aluvm = { git = "https://github.com/AluVM/rust-aluvm" }
rgb-core = { git = "https://github.com/RGB-WG/rgb-core", branch = "revision" }
42 changes: 42 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,33 @@
// See the License for the specific language governing permissions and
// limitations under the License.

//! Library provides three main procedures:
//!
//! ## 1. PSBT-based state transition construction.
//!
//! Given PSBT-originating set of outpoints the procedure creates all required
//! state transitions for all contracts, adding necessary information to PSBT
//! for constructing bundles and tapret proofs. The actual state transitions are
//! saved into the stash even before witness transactions are mined. They may be
//! also put into PSBT, if needed for the hardware signers.
//!
//! ## 2. PSBT-based finalization.
//!
//! Procedure takes PSBT with all information for constructing transition
//! bundles and taprets and
//! a) generates final tapret commitment;
//! b) creates consignment for the main transfer.
//!
//! ## 3. Descriptor-based contract state.
//!
//! Checks descriptor UTXO set and updates contract, removing outdated outputs.
//! For instance, after consignment creation, a new state transition is already
//! present in the contract state, even before the witness transaction is mined.
//! Descriptor filtering of the contract state will show a valid result, since
//! a new state without mined witness will not be displayed. Once the witness
//! gets mined, a new state appears, and previous state gets invalidated since
//! it no longer assigned to an unspent transaction output.
#![deny(
non_upper_case_globals,
non_camel_case_types,
Expand All @@ -29,3 +56,18 @@
//missing_docs
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]

#[macro_use]
extern crate amplify;

pub mod psbt;

// 1. Construct main state transition with transition builder
// -- shortcut using invoice to do that construction (like .with_invoice())
// -- have a construction for the "remaining state" assigned to a seal
// prototype.
// 2. Add that state transition to PSBT
// -- add change by checking change PSBT flag and assigning remaining state to
// that output
// 3. Extract from PSBT all spent prevouts and construct blank state transitions
// for each one of them; embed them into PSBT
20 changes: 0 additions & 20 deletions src/psbt.rs

This file was deleted.

241 changes: 241 additions & 0 deletions src/psbt/lnpbp4.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
// RGB wallet library for smart contracts on Bitcoin & Lightning network
//
// SPDX-License-Identifier: Apache-2.0
//
// Written in 2019-2023 by
// Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
//
// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::collections::BTreeMap;

use amplify::confinement;
use amplify::confinement::Confined;
use bitcoin::psbt::raw::ProprietaryKey;
use bitcoin::psbt::Output;
use commit_verify::mpc::{self, Message, ProtocolId};

/// PSBT proprietary key prefix used for LNPBP4 commitment-related data.
pub const PSBT_LNPBP4_PREFIX: &[u8] = b"LNPBP4";

/// Proprietary key subtype for storing LNPBP4 single commitment message under
/// some protocol in global map.
pub const PSBT_OUT_LNPBP4_MESSAGE: u8 = 0x00;
/// Proprietary key subtype for storing LNPBP4 entropy constant.
pub const PSBT_OUT_LNPBP4_ENTROPY: u8 = 0x01;
/// Proprietary key subtype for storing LNPBP4 requirement for a minimal tree
/// size.
pub const PSBT_OUT_LNPBP4_MIN_TREE_DEPTH: u8 = 0x02;

/// Extension trait for static functions returning LNPBP4-related proprietary
/// keys.
pub trait ProprietaryKeyLnpbp4 {
fn lnpbp4_message(protocol_id: ProtocolId) -> ProprietaryKey;
fn lnpbp4_entropy() -> ProprietaryKey;
fn lnpbp4_min_tree_depth() -> ProprietaryKey;
}

impl ProprietaryKeyLnpbp4 for ProprietaryKey {
/// Constructs [`PSBT_OUT_LNPBP4_MESSAGE`] proprietary key.
fn lnpbp4_message(protocol_id: ProtocolId) -> ProprietaryKey {
ProprietaryKey {
prefix: PSBT_LNPBP4_PREFIX.to_vec(),
subtype: PSBT_OUT_LNPBP4_MESSAGE,
key: protocol_id.to_vec(),
}
}

/// Constructs [`PSBT_OUT_LNPBP4_ENTROPY`] proprietary key.
fn lnpbp4_entropy() -> ProprietaryKey {
ProprietaryKey {
prefix: PSBT_LNPBP4_PREFIX.to_vec(),
subtype: PSBT_OUT_LNPBP4_ENTROPY,
key: empty!(),
}
}

/// Constructs [`PSBT_OUT_LNPBP4_MIN_TREE_DEPTH`] proprietary key.
fn lnpbp4_min_tree_depth() -> ProprietaryKey {
ProprietaryKey {
prefix: PSBT_LNPBP4_PREFIX.to_vec(),
subtype: PSBT_OUT_LNPBP4_MIN_TREE_DEPTH,
key: empty!(),
}
}
}

/// Errors processing LNPBP4-related proprietary PSBT keys and their values.
#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error, From)]
#[display(doc_comments)]
pub enum Lnpbp4PsbtError {
/// the key contains invalid value.
#[from(bitcoin::hashes::Error)]
InvalidKeyValue,

/// message map produced from PSBT inputs exceeds maximum size bounds.
#[from]
MessageMapTooLarge(confinement::Error),

/// the key is already present, but has a different value.
AlreadySet,
}

pub trait OutputLnpbp4 {
fn lnpbp4_message_map(&self) -> Result<mpc::MessageMap, Lnpbp4PsbtError>;
fn lnpbp4_message(&self, protocol_id: ProtocolId) -> Option<Message>;
fn lnpbp4_entropy(&self) -> Option<u64>;
fn lnpbp4_min_tree_depth(&self) -> Option<u8>;
fn set_lnpbp4_message(
&mut self,
protocol_id: ProtocolId,
message: Message,
) -> Result<bool, Lnpbp4PsbtError>;
fn set_lnpbp4_entropy(&mut self, entropy: u64) -> Result<bool, Lnpbp4PsbtError>;
fn set_lnpbp4_min_tree_depth(&mut self, min_depth: u8) -> Option<u8>;
}

/// Extension trait for [`Output`] for working with proprietary LNPBP4
/// keys.
impl OutputLnpbp4 for Output {
/// Returns [`lnpbp4::MessageMap`] constructed from the proprietary key
/// data.
fn lnpbp4_message_map(&self) -> Result<mpc::MessageMap, Lnpbp4PsbtError> {
let map = self
.proprietary
.iter()
.filter(|(key, _)| {
// TODO: Error when only a single key is present
key.prefix == PSBT_LNPBP4_PREFIX && key.subtype == PSBT_OUT_LNPBP4_MESSAGE
})
.map(|(key, val)| {
Ok((
ProtocolId::from_slice(&key.key).ok_or(Lnpbp4PsbtError::InvalidKeyValue)?,
Message::from_slice(val).ok_or(Lnpbp4PsbtError::InvalidKeyValue)?,
))
})
.collect::<Result<BTreeMap<_, _>, Lnpbp4PsbtError>>()?;
Confined::try_from(map).map_err(Lnpbp4PsbtError::from)
}

/// Returns a valid LNPBP-4 [`Message`] associated with the given
/// [`ProtocolId`], if any.
///
/// We do not error on invalid data in order to support future update of
/// this proprietary key to a standard one. In this case, the invalid
/// data will be filtered at the moment of PSBT deserialization and this
/// function will return `None` only in situations when the key is absent.
fn lnpbp4_message(&self, protocol_id: ProtocolId) -> Option<Message> {
let key = ProprietaryKey::lnpbp4_message(protocol_id);
let data = self.proprietary.get(&key)?;
Message::from_slice(data)
}

/// Returns a valid LNPBP-4 entropy value, if present.
///
/// We do not error on invalid data in order to support future update of
/// this proprietary key to a standard one. In this case, the invalid
/// data will be filtered at the moment of PSBT deserialization and this
/// function will return `None` only in situations when the key is absent.
fn lnpbp4_entropy(&self) -> Option<u64> {
let key = ProprietaryKey::lnpbp4_entropy();
let data = self.proprietary.get(&key)?;
if data.len() != 8 {
return None;
}
let mut buf = [0u8; 8];
buf.copy_from_slice(data);
Some(u64::from_le_bytes(buf))
}

/// Returns a valid LNPBP-4 minimal tree depth value, if present.
///
/// # Errors
///
/// If the key is present, but it's value can't be deserialized as a valid
/// minimal tree depth value.
fn lnpbp4_min_tree_depth(&self) -> Option<u8> {
let key = ProprietaryKey::lnpbp4_min_tree_depth();
let data = self.proprietary.get(&key)?;
if data.len() != 1 {
return None;
}
Some(data[0])
}

/// Sets LNPBP4 [`Message`] for the given [`ProtocolId`].
///
/// # Returns
///
/// `true`, if the message was set successfully, `false` if this message was
/// already present for this protocol.
///
/// # Errors
///
/// If the key for the given [`ProtocolId`] is already present and the
/// message is different.
fn set_lnpbp4_message(
&mut self,
protocol_id: ProtocolId,
message: Message,
) -> Result<bool, Lnpbp4PsbtError> {
let key = ProprietaryKey::lnpbp4_message(protocol_id);
let val = message.to_vec();
if let Some(v) = self.proprietary.get(&key) {
if v != &val {
return Err(Lnpbp4PsbtError::InvalidKeyValue);
}
return Ok(false);
}
self.proprietary.insert(key, val);
Ok(true)
}

/// Sets LNPBP4 entropy value.
///
/// # Returns
///
/// `true`, if the entropy was set successfully, `false` if this entropy
/// value was already set.
///
/// # Errors
///
/// If the entropy was already set with a different value than the provided
/// one.
fn set_lnpbp4_entropy(&mut self, entropy: u64) -> Result<bool, Lnpbp4PsbtError> {
let key = ProprietaryKey::lnpbp4_entropy();
let val = entropy.to_le_bytes().to_vec();
if let Some(v) = self.proprietary.get(&key) {
if v != &val {
return Err(Lnpbp4PsbtError::InvalidKeyValue);
}
return Ok(false);
}
self.proprietary.insert(key, val);
Ok(true)
}

/// Sets LNPBP4 min tree depth value.
///
/// # Returns
///
/// Previous minimal tree depth value, if it was present and valid - or None
/// if the value was absent or invalid (the new value is still assigned).
fn set_lnpbp4_min_tree_depth(&mut self, min_depth: u8) -> Option<u8> {
let key = ProprietaryKey::lnpbp4_min_tree_depth();
let val = vec![min_depth];
let prev = self.lnpbp4_min_tree_depth();
self.proprietary.insert(key, val).and_then(|_| prev)
}
}
51 changes: 51 additions & 0 deletions src/psbt/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// RGB wallet library for smart contracts on Bitcoin & Lightning network
//
// SPDX-License-Identifier: Apache-2.0
//
// Written in 2019-2023 by
// Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
//
// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Managing RGB-related proprietary keys inside PSBT.
//!
//! Supports Tapret, Opret, P2C and S2C commitments and LNPBP4 structures used
//! by all of them.
// TODO: Move to BP wallet
mod lnpbp4;
// TODO: Move to BP wallet
mod opret;
// TODO: Move to BP wallet
mod tapret;
mod rgb;

pub use lnpbp4::{
Lnpbp4PsbtError, ProprietaryKeyLnpbp4, PSBT_LNPBP4_PREFIX, PSBT_OUT_LNPBP4_ENTROPY,
PSBT_OUT_LNPBP4_MESSAGE, PSBT_OUT_LNPBP4_MIN_TREE_DEPTH,
};
pub use opret::{
OpretKeyError, ProprietaryKeyOpret, PSBT_OPRET_PREFIX, PSBT_OUT_OPRET_COMMITMENT,
PSBT_OUT_OPRET_HOST,
};
pub use tapret::{
ProprietaryKeyTapret, TapretKeyError, PSBT_IN_TAPRET_TWEAK, PSBT_OUT_TAPRET_COMMITMENT,
PSBT_OUT_TAPRET_HOST, PSBT_OUT_TAPRET_PROOF, PSBT_TAPRET_PREFIX,
};

pub use self::rgb::{
ProprietaryKeyRgb, RgbExt, RgbInExt, RgbPsbtError, PSBT_GLOBAL_RGB_TRANSITION,
PSBT_IN_RGB_CONSUMED_BY, PSBT_RGB_PREFIX,
};
Loading

0 comments on commit 675ac2a

Please sign in to comment.