From 3f86c299cd46604edd8690ef4914ceb9c9ded0b1 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Wed, 29 Jan 2025 10:10:33 +0100 Subject: [PATCH 01/12] fix bug in resolving seals --- src/verify.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/verify.rs b/src/verify.rs index 6d98e4ed..28de4405 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -25,7 +25,7 @@ use alloc::collections::BTreeMap; use std::collections::BTreeSet; -use amplify::confinement::SmallVec; +use amplify::confinement::SmallOrdMap; use amplify::ByteArray; use bp::seals::mmb; use single_use_seals::{PublishedWitness, SealError, SealWitness}; @@ -55,7 +55,7 @@ pub struct OperationSeals { pub operation: Operation, /// Operation itself contains only AuthToken's, which are a commitments to the seals. Hence, we /// have to separately include a full seal definitions next to the operation data. - pub defined_seals: SmallVec, + pub defined_seals: SmallOrdMap, } pub trait ReadOperation: Sized { @@ -120,8 +120,7 @@ pub trait ContractVerify: ContractApi { let iter = header .defined_seals .iter() - .enumerate() - .map(|(pos, seal)| (CellAddr::new(opid, pos as u16), seal.clone())); + .map(|(pos, seal)| (CellAddr::new(opid, *pos), seal.clone())); // We need to check that all seal definitions strictly match operation-defined destructible cells let defined = header From 0aada4092f53f2851d9001c3bf52b2baab2f1747 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Wed, 29 Jan 2025 16:07:17 +0100 Subject: [PATCH 02/12] distinguish seal definitions from sources --- Cargo.lock | 123 ++++++++++++++++++++++++++------------------------ Cargo.toml | 10 ++-- src/lib.rs | 2 +- src/seals.rs | 29 ++++++++++-- src/verify.rs | 102 ++++++++++++++++++++++------------------- 5 files changed, 149 insertions(+), 117 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73e2e01e..5e3e2b07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -157,7 +157,7 @@ dependencies = [ [[package]] name = "bp-consensus" version = "0.12.0-beta.4" -source = "git+https://github.com/BP-WG/bp-core?branch=refactor/consensus#1645008e3aa31c762747a159877a2cd4550de933" +source = "git+https://github.com/BP-WG/bp-core?branch=wtxoseal#1487568dba4ca18b15dc211583060a7fb01096e7" dependencies = [ "amplify", "chrono", @@ -169,7 +169,7 @@ dependencies = [ [[package]] name = "bp-core" version = "0.12.0-beta.4" -source = "git+https://github.com/BP-WG/bp-core?branch=refactor/consensus#1645008e3aa31c762747a159877a2cd4550de933" +source = "git+https://github.com/BP-WG/bp-core?branch=wtxoseal#1487568dba4ca18b15dc211583060a7fb01096e7" dependencies = [ "amplify", "bp-consensus", @@ -185,7 +185,7 @@ dependencies = [ [[package]] name = "bp-dbc" version = "0.12.0-beta.4" -source = "git+https://github.com/BP-WG/bp-core?branch=refactor/consensus#1645008e3aa31c762747a159877a2cd4550de933" +source = "git+https://github.com/BP-WG/bp-core?branch=wtxoseal#1487568dba4ca18b15dc211583060a7fb01096e7" dependencies = [ "amplify", "base85", @@ -198,7 +198,7 @@ dependencies = [ [[package]] name = "bp-seals" version = "0.12.0-beta.4" -source = "git+https://github.com/BP-WG/bp-core?branch=refactor/consensus#1645008e3aa31c762747a159877a2cd4550de933" +source = "git+https://github.com/BP-WG/bp-core?branch=wtxoseal#1487568dba4ca18b15dc211583060a7fb01096e7" dependencies = [ "amplify", "baid64", @@ -212,9 +212,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byteorder" @@ -224,9 +224,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.5" +version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ "shlex", ] @@ -289,9 +289,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -391,9 +391,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown", @@ -401,9 +401,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -417,9 +417,9 @@ checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "minicov" @@ -469,18 +469,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -541,6 +541,12 @@ dependencies = [ "digest", ] +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + [[package]] name = "same-file" version = "1.0.6" @@ -550,12 +556,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "secp256k1" version = "0.30.0" @@ -578,22 +578,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.96", ] [[package]] @@ -696,9 +696,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.91" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -722,7 +722,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.96", ] [[package]] @@ -734,7 +734,7 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ultrasonic" version = "0.12.0-beta.4" -source = "git+https://github.com/AluVM/ultrasonic?branch=refactor/consensus#e214dcb8955604c044f238aaf8a74b4413b02d0e" +source = "git+https://github.com/AluVM/ultrasonic?branch=master#2bb7be96e316746363ff3e4f1f2ccb1c8496a94a" dependencies = [ "amplify", "baid64", @@ -748,9 +748,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "version_check" @@ -786,34 +786,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.96", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.49" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", @@ -824,9 +825,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -834,32 +835,34 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.96", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-bindgen-test" -version = "0.3.49" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d44563646eb934577f2772656c7ad5e9c90fac78aa8013d776fcdaf24625d" +checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" dependencies = [ "js-sys", "minicov", - "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", @@ -867,20 +870,20 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.49" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54171416ce73aa0b9c377b51cc3cb542becee1cd678204812e8392e5b0e4a031" +checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.96", ] [[package]] name = "web-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -995,7 +998,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.96", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 80ac3482..1001d187 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,8 +54,8 @@ wasm-bindgen-test = "0.3" features = ["all"] [patch.crates-io] -ultrasonic = { git = "https://github.com/AluVM/ultrasonic", branch = "refactor/consensus" } -bp-consensus = { git = "https://github.com/BP-WG/bp-core", branch = "refactor/consensus" } -bp-dbc = { git = "https://github.com/BP-WG/bp-core", branch = "refactor/consensus" } -bp-seals = { git = "https://github.com/BP-WG/bp-core", branch = "refactor/consensus" } -bp-core = { git = "https://github.com/BP-WG/bp-core", branch = "refactor/consensus" } +ultrasonic = { git = "https://github.com/AluVM/ultrasonic", branch = "master" } +bp-consensus = { git = "https://github.com/BP-WG/bp-core", branch = "wtxoseal" } +bp-dbc = { git = "https://github.com/BP-WG/bp-core", branch = "wtxoseal" } +bp-seals = { git = "https://github.com/BP-WG/bp-core", branch = "wtxoseal" } +bp-core = { git = "https://github.com/BP-WG/bp-core", branch = "wtxoseal" } diff --git a/src/lib.rs b/src/lib.rs index 53162ca4..ab818266 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,7 +38,7 @@ extern crate serde; mod verify; mod seals; -pub use seals::{RgbSeal, SealAuthToken}; +pub use seals::{RgbSealDef, RgbSealSrc}; pub use single_use_seals::*; pub use verify::{ContractApi, ContractVerify, OperationSeals, ReadOperation, ReadWitness, Step, VerificationError}; diff --git a/src/seals.rs b/src/seals.rs index 1a8b7fed..cfd7f704 100644 --- a/src/seals.rs +++ b/src/seals.rs @@ -22,28 +22,36 @@ // or implied. See the License for the specific language governing permissions and limitations under // the License. +use core::fmt::{Debug, Display}; + use bp::seals::mmb; use single_use_seals::SingleUseSeal; +use strict_encoding::{StrictDecode, StrictDumb, StrictEncode}; use ultrasonic::AuthToken; -pub trait SealAuthToken { +pub trait RgbSealDef: Clone + Debug + Display + StrictDumb + StrictEncode + StrictDecode { + type Src: RgbSealSrc; fn auth_token(&self) -> AuthToken; + fn resolve(&self, witness: &::PubWitness) -> Self::Src; } -pub trait RgbSeal: SingleUseSeal + SealAuthToken {} +pub trait RgbSealSrc: SingleUseSeal + Ord {} // Below are capabilities constants used in the standard library: #[cfg(any(feature = "bitcoin", feature = "liquid"))] pub mod bitcoin { - use bp::seals::TxoSeal; + use bp::seals::{TxoSeal, WOutpoint, WTxoSeal}; + use bp::Outpoint; use commit_verify::CommitId; use super::*; - impl RgbSeal for TxoSeal {} + impl RgbSealSrc for TxoSeal {} + + impl RgbSealDef for WTxoSeal { + type Src = TxoSeal; - impl SealAuthToken for TxoSeal { // SECURITY: Here we cut SHA256 tagged hash of a single-use seal definition to 30 bytes in order // to fit it into a field element with no overflows. This must be a secure operation since we // still have a sufficient 120-bit collision resistance. @@ -53,5 +61,16 @@ pub mod bitcoin { shortened_id.copy_from_slice(&id[0..30]); AuthToken::from_byte_array(shortened_id) } + + fn resolve(&self, witness: &::PubWitness) -> Self::Src { + let primary = match self.primary { + WOutpoint::Wout(wout) => { + debug_assert!(witness.outputs.len() > wout.to_usize()); + Outpoint::new(witness.txid(), wout) + } + WOutpoint::Extern(outpoint) => outpoint, + }; + TxoSeal { primary, secondary: self.secondary } + } } } diff --git a/src/verify.rs b/src/verify.rs index 28de4405..a4f3ff80 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -22,16 +22,16 @@ // or implied. See the License for the specific language governing permissions and limitations under // the License. -use alloc::collections::BTreeMap; -use std::collections::BTreeSet; +use alloc::collections::{BTreeMap, BTreeSet}; +use alloc::vec::Vec; use amplify::confinement::SmallOrdMap; use amplify::ByteArray; use bp::seals::mmb; -use single_use_seals::{PublishedWitness, SealError, SealWitness}; +use single_use_seals::{PublishedWitness, SealError, SealWitness, SingleUseSeal}; use ultrasonic::{CallError, CellAddr, Codex, ContractId, LibRepo, Memory, Operation, Opid}; -use crate::{RgbSeal, LIB_NAME_RGB_CORE}; +use crate::{RgbSealDef, RgbSealSrc, LIB_NAME_RGB_CORE}; // TODO: Move to amplify crate pub enum Step { @@ -45,13 +45,9 @@ pub enum Step { #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), - serde( - rename_all = "camelCase", - bound = "Seal: serde::Serialize + for<'d> serde::Deserialize<'d>, Seal::PubWitness: serde::Serialize + \ - for<'d> serde::Deserialize<'d>, Seal::CliWitness: serde::Serialize + for<'d> serde::Deserialize<'d>" - ) + serde(rename_all = "camelCase", bound = "Seal: serde::Serialize + for<'d> serde::Deserialize<'d>") )] -pub struct OperationSeals { +pub struct OperationSeals { pub operation: Operation, /// Operation itself contains only AuthToken's, which are a commitments to the seals. Hence, we /// have to separately include a full seal definitions next to the operation data. @@ -59,13 +55,13 @@ pub struct OperationSeals { } pub trait ReadOperation: Sized { - type Seal: RgbSeal; - type WitnessReader: ReadWitness; + type Seal: RgbSealDef; + type WitnessReader: ReadWitness::Src, OpReader = Self>; fn read_operation(self) -> Option<(OperationSeals, Self::WitnessReader)>; } pub trait ReadWitness: Sized { - type Seal: RgbSeal; + type Seal: RgbSealSrc; type OpReader: ReadOperation; fn read_witness(self) -> Step<(SealWitness, Self), Self::OpReader>; } @@ -74,24 +70,24 @@ pub trait ReadWitness: Sized { /// [`ContractVerify`]). /// /// NB: `apply_operation` is called only after `apply_witness`. -pub trait ContractApi { +pub trait ContractApi { fn contract_id(&self) -> ContractId; fn codex(&self) -> &Codex; fn repo(&self) -> &impl LibRepo; fn memory(&self) -> &impl Memory; fn apply_operation(&mut self, header: OperationSeals); - fn apply_witness(&mut self, opid: Opid, witness: SealWitness); + fn apply_witness(&mut self, opid: Opid, witness: SealWitness); } // We use dedicated trait here in order to prevent overriding of the implementation in client // libraries -pub trait ContractVerify: ContractApi { +pub trait ContractVerify: ContractApi { // TODO: Support multi-thread mode for parallel processing of unrelated operations fn evaluate>(&mut self, mut reader: R) -> Result<(), VerificationError> { let contract_id = self.contract_id(); let mut first = true; - let mut seals = BTreeMap::::new(); + let mut seals = BTreeMap::::new(); while let Some((mut header, mut witness_reader)) = reader.read_operation() { // Genesis can't commit to the contract id since the contract doesn't exist yet; thus, we have to // apply this little trick @@ -109,43 +105,35 @@ pub trait ContractVerify: ContractApi { // Next we verify its single-use seals let opid = header.operation.opid(); - let mut closed_seals = alloc::vec![]; + let mut closed_seals = Vec::::new(); for input in &header.operation.destroying { - let Some(seal) = seals.remove(&input.addr) else { - return Err(VerificationError::SealUnknown(input.addr)); - }; + let seal = seals + .remove(&input.addr) + .ok_or(VerificationError::SealUnknown(input.addr))?; closed_seals.push(seal); } - let iter = header - .defined_seals - .iter() - .map(|(pos, seal)| (CellAddr::new(opid, *pos), seal.clone())); - - // We need to check that all seal definitions strictly match operation-defined destructible cells - let defined = header - .operation - .destructible - .iter() - .map(|cell| cell.auth.to_byte_array()) - .collect::>(); - let sealed = iter - .clone() - .map(|(_, seal)| seal.auth_token().to_byte_array()) - .collect::>(); - if !sealed.is_subset(&defined) { - return Err(VerificationError::SealsDefinitionMismatch(opid)); - } - // This convoluted logic happens since we use a state machine which ensures the client can't lie to // the verifier let mut witness_count = 0usize; + let mut seal_sources = BTreeSet::new(); loop { + // An operation may have multiple witnesses (like multiple commitment transactions in lightning + // channel). match witness_reader.read_witness() { Step::Next((witness, w)) => { + let msg = mmb::Message::from_byte_array(opid.to_byte_array()); witness - .verify_seals_closing(&closed_seals, mmb::Message::from_byte_array(opid.to_byte_array())) + .verify_seals_closing(&closed_seals, msg) .map_err(|e| VerificationError::SealsNotClosed(witness.published.pub_id(), opid, e))?; + + // Each witness actually produces its own set of witness-output based seal sources. + let iter = header + .defined_seals + .iter() + .map(|(pos, seal)| (CellAddr::new(opid, *pos), seal.resolve(&witness.published))); + seal_sources.extend(iter); + self.apply_witness(opid, witness); witness_reader = w; } @@ -157,11 +145,29 @@ pub trait ContractVerify: ContractApi { witness_count += 1; } + // We need to check that all seal definitions strictly match operation-defined destructible cells + let defined = header + .operation + .destructible + .iter() + .map(|cell| cell.auth.to_byte_array()) + .collect::>(); + let sealed = header + .defined_seals + .values() + .map(|seal| seal.auth_token().to_byte_array()) + .collect::>(); + // It is a subset and not equal set since some of the seals might be unknown to us: we know their + // commitment auth token, but do not know definition. + if !sealed.is_subset(&defined) { + return Err(VerificationError::SealsDefinitionMismatch(opid)); + } + if !closed_seals.is_empty() && witness_count == 0 { return Err(VerificationError::NoWitness(opid)); } - seals.extend(iter); + seals.extend(seal_sources); if first { first = false } else { @@ -173,12 +179,12 @@ pub trait ContractVerify: ContractApi { } } -impl> ContractVerify for C {} +impl> ContractVerify for C {} // TODO: Find a way to do Debug and Clone implementation #[derive(Debug, Display, From)] #[display(doc_comments)] -pub enum VerificationError { +pub enum VerificationError { /// genesis does not commit to the codex id; a wrong contract genesis is used. NoCodexCommitment, @@ -188,7 +194,11 @@ pub enum VerificationError { /// single-use seals are not closed properly with witness {0} for operation {1}. /// /// Details: {2} - SealsNotClosed(>::PubId, Opid, SealError), + SealsNotClosed( + <::PubWitness as PublishedWitness>::PubId, + Opid, + SealError, + ), /// unknown seal definition for cell address {0}. SealUnknown(CellAddr), From ed716baf560905df8892a7de8283141896535225 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 30 Jan 2025 09:36:48 +0100 Subject: [PATCH 03/12] fix SealDef/SealSrc relations in readers --- src/verify.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/verify.rs b/src/verify.rs index a4f3ff80..aac2bd22 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -31,7 +31,7 @@ use bp::seals::mmb; use single_use_seals::{PublishedWitness, SealError, SealWitness, SingleUseSeal}; use ultrasonic::{CallError, CellAddr, Codex, ContractId, LibRepo, Memory, Operation, Opid}; -use crate::{RgbSealDef, RgbSealSrc, LIB_NAME_RGB_CORE}; +use crate::{RgbSealDef, LIB_NAME_RGB_CORE}; // TODO: Move to amplify crate pub enum Step { @@ -55,15 +55,15 @@ pub struct OperationSeals { } pub trait ReadOperation: Sized { - type Seal: RgbSealDef; - type WitnessReader: ReadWitness::Src, OpReader = Self>; - fn read_operation(self) -> Option<(OperationSeals, Self::WitnessReader)>; + type SealDef: RgbSealDef; + type WitnessReader: ReadWitness; + fn read_operation(self) -> Option<(OperationSeals, Self::WitnessReader)>; } pub trait ReadWitness: Sized { - type Seal: RgbSealSrc; - type OpReader: ReadOperation; - fn read_witness(self) -> Step<(SealWitness, Self), Self::OpReader>; + type SealDef: RgbSealDef; + type OperationReader: ReadOperation; + fn read_witness(self) -> Step<(SealWitness<::Src>, Self), Self::OperationReader>; } /// API exposed by the contract required for evaluating and verifying the contract state (see @@ -81,13 +81,16 @@ pub trait ContractApi { // We use dedicated trait here in order to prevent overriding of the implementation in client // libraries -pub trait ContractVerify: ContractApi { +pub trait ContractVerify: ContractApi { // TODO: Support multi-thread mode for parallel processing of unrelated operations - fn evaluate>(&mut self, mut reader: R) -> Result<(), VerificationError> { + fn evaluate>( + &mut self, + mut reader: R, + ) -> Result<(), VerificationError> { let contract_id = self.contract_id(); let mut first = true; - let mut seals = BTreeMap::::new(); + let mut seals = BTreeMap::::new(); while let Some((mut header, mut witness_reader)) = reader.read_operation() { // Genesis can't commit to the contract id since the contract doesn't exist yet; thus, we have to // apply this little trick @@ -105,7 +108,7 @@ pub trait ContractVerify: ContractApi { // Next we verify its single-use seals let opid = header.operation.opid(); - let mut closed_seals = Vec::::new(); + let mut closed_seals = Vec::::new(); for input in &header.operation.destroying { let seal = seals .remove(&input.addr) @@ -179,7 +182,7 @@ pub trait ContractVerify: ContractApi { } } -impl> ContractVerify for C {} +impl> ContractVerify for C {} // TODO: Find a way to do Debug and Clone implementation #[derive(Debug, Display, From)] From c5a589663b3a09be3fcd51973c907f8b88d02612 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 30 Jan 2025 09:41:47 +0100 Subject: [PATCH 04/12] fix seal type in VerificationError --- src/verify.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/verify.rs b/src/verify.rs index aac2bd22..99b3db15 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -28,10 +28,10 @@ use alloc::vec::Vec; use amplify::confinement::SmallOrdMap; use amplify::ByteArray; use bp::seals::mmb; -use single_use_seals::{PublishedWitness, SealError, SealWitness, SingleUseSeal}; +use single_use_seals::{PublishedWitness, SealError, SealWitness}; use ultrasonic::{CallError, CellAddr, Codex, ContractId, LibRepo, Memory, Operation, Opid}; -use crate::{RgbSealDef, LIB_NAME_RGB_CORE}; +use crate::{RgbSealDef, RgbSealSrc, LIB_NAME_RGB_CORE}; // TODO: Move to amplify crate pub enum Step { @@ -86,7 +86,7 @@ pub trait ContractVerify: ContractApi { fn evaluate>( &mut self, mut reader: R, - ) -> Result<(), VerificationError> { + ) -> Result<(), VerificationError> { let contract_id = self.contract_id(); let mut first = true; @@ -187,7 +187,7 @@ impl> ContractVerify for C // TODO: Find a way to do Debug and Clone implementation #[derive(Debug, Display, From)] #[display(doc_comments)] -pub enum VerificationError { +pub enum VerificationError { /// genesis does not commit to the codex id; a wrong contract genesis is used. NoCodexCommitment, @@ -197,11 +197,7 @@ pub enum VerificationError { /// single-use seals are not closed properly with witness {0} for operation {1}. /// /// Details: {2} - SealsNotClosed( - <::PubWitness as PublishedWitness>::PubId, - Opid, - SealError, - ), + SealsNotClosed(>::PubId, Opid, SealError), /// unknown seal definition for cell address {0}. SealUnknown(CellAddr), From 43d885ffee9d6bce56ea400e5dbf90878d25b443 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 30 Jan 2025 10:17:46 +0100 Subject: [PATCH 05/12] use witness id in seal resolver --- src/seals.rs | 17 ++++++++++------- src/verify.rs | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/seals.rs b/src/seals.rs index cfd7f704..0f2c19e8 100644 --- a/src/seals.rs +++ b/src/seals.rs @@ -25,14 +25,17 @@ use core::fmt::{Debug, Display}; use bp::seals::mmb; -use single_use_seals::SingleUseSeal; +use single_use_seals::{PublishedWitness, SingleUseSeal}; use strict_encoding::{StrictDecode, StrictDumb, StrictEncode}; use ultrasonic::AuthToken; pub trait RgbSealDef: Clone + Debug + Display + StrictDumb + StrictEncode + StrictDecode { type Src: RgbSealSrc; fn auth_token(&self) -> AuthToken; - fn resolve(&self, witness: &::PubWitness) -> Self::Src; + fn resolve( + &self, + witness_id: <::PubWitness as PublishedWitness>::PubId, + ) -> Self::Src; } pub trait RgbSealSrc: SingleUseSeal + Ord {} @@ -62,12 +65,12 @@ pub mod bitcoin { AuthToken::from_byte_array(shortened_id) } - fn resolve(&self, witness: &::PubWitness) -> Self::Src { + fn resolve( + &self, + witness_id: <::PubWitness as PublishedWitness>::PubId, + ) -> Self::Src { let primary = match self.primary { - WOutpoint::Wout(wout) => { - debug_assert!(witness.outputs.len() > wout.to_usize()); - Outpoint::new(witness.txid(), wout) - } + WOutpoint::Wout(wout) => Outpoint::new(witness_id, wout), WOutpoint::Extern(outpoint) => outpoint, }; TxoSeal { primary, secondary: self.secondary } diff --git a/src/verify.rs b/src/verify.rs index 99b3db15..3d77ee68 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -134,7 +134,7 @@ pub trait ContractVerify: ContractApi { let iter = header .defined_seals .iter() - .map(|(pos, seal)| (CellAddr::new(opid, *pos), seal.resolve(&witness.published))); + .map(|(pos, seal)| (CellAddr::new(opid, *pos), seal.resolve(witness.published.pub_id()))); seal_sources.extend(iter); self.apply_witness(opid, witness); From 0aabcc42d53ee4202bcc0e559b9b74c6d0a0b89f Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 30 Jan 2025 11:20:28 +0100 Subject: [PATCH 06/12] add RgbSealDef::to_src method --- src/seals.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/seals.rs b/src/seals.rs index 0f2c19e8..696121ed 100644 --- a/src/seals.rs +++ b/src/seals.rs @@ -36,6 +36,7 @@ pub trait RgbSealDef: Clone + Debug + Display + StrictDumb + StrictEncode + Stri &self, witness_id: <::PubWitness as PublishedWitness>::PubId, ) -> Self::Src; + fn to_src(&self) -> Option; } pub trait RgbSealSrc: SingleUseSeal + Ord {} @@ -75,5 +76,13 @@ pub mod bitcoin { }; TxoSeal { primary, secondary: self.secondary } } + + fn to_src(&self) -> Option { + let primary = match self.primary { + WOutpoint::Wout(_) => return None, + WOutpoint::Extern(outpoint) => outpoint, + }; + Some(TxoSeal { primary, secondary: self.secondary }) + } } } From 667d9f34efae5beb2f08424e1df4c4a66f6c07f2 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 30 Jan 2025 12:45:23 +0100 Subject: [PATCH 07/12] re-order steps of verification --- src/verify.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/verify.rs b/src/verify.rs index 3d77ee68..9de681e0 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -116,6 +116,24 @@ pub trait ContractVerify: ContractApi { closed_seals.push(seal); } + // We need to check that all seal definitions strictly match operation-defined destructible cells + let defined = header + .operation + .destructible + .iter() + .map(|cell| cell.auth.to_byte_array()) + .collect::>(); + let sealed = header + .defined_seals + .values() + .map(|seal| seal.auth_token().to_byte_array()) + .collect::>(); + // It is a subset and not equal set since some of the seals might be unknown to us: we know their + // commitment auth token, but do not know definition. + if !sealed.is_subset(&defined) { + return Err(VerificationError::SealsDefinitionMismatch(opid)); + } + // This convoluted logic happens since we use a state machine which ensures the client can't lie to // the verifier let mut witness_count = 0usize; @@ -148,24 +166,6 @@ pub trait ContractVerify: ContractApi { witness_count += 1; } - // We need to check that all seal definitions strictly match operation-defined destructible cells - let defined = header - .operation - .destructible - .iter() - .map(|cell| cell.auth.to_byte_array()) - .collect::>(); - let sealed = header - .defined_seals - .values() - .map(|seal| seal.auth_token().to_byte_array()) - .collect::>(); - // It is a subset and not equal set since some of the seals might be unknown to us: we know their - // commitment auth token, but do not know definition. - if !sealed.is_subset(&defined) { - return Err(VerificationError::SealsDefinitionMismatch(opid)); - } - if !closed_seals.is_empty() && witness_count == 0 { return Err(VerificationError::NoWitness(opid)); } From 6c2800130a0d13ef67f61d6b27be59f46e712739 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 30 Jan 2025 12:46:15 +0100 Subject: [PATCH 08/12] chore: fix clippy lint --- src/verify.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/verify.rs b/src/verify.rs index 9de681e0..00896373 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -63,6 +63,7 @@ pub trait ReadOperation: Sized { pub trait ReadWitness: Sized { type SealDef: RgbSealDef; type OperationReader: ReadOperation; + #[allow(clippy::type_complexity)] fn read_witness(self) -> Step<(SealWitness<::Src>, Self), Self::OperationReader>; } From a8e12937219fe8ca19370ae9fd9d605903d082fb Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 30 Jan 2025 12:51:53 +0100 Subject: [PATCH 09/12] chore: fix no-defaults builds --- src/seals.rs | 3 +-- src/verify.rs | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/seals.rs b/src/seals.rs index 696121ed..e7fbcdaf 100644 --- a/src/seals.rs +++ b/src/seals.rs @@ -24,7 +24,6 @@ use core::fmt::{Debug, Display}; -use bp::seals::mmb; use single_use_seals::{PublishedWitness, SingleUseSeal}; use strict_encoding::{StrictDecode, StrictDumb, StrictEncode}; use ultrasonic::AuthToken; @@ -39,7 +38,7 @@ pub trait RgbSealDef: Clone + Debug + Display + StrictDumb + StrictEncode + Stri fn to_src(&self) -> Option; } -pub trait RgbSealSrc: SingleUseSeal + Ord {} +pub trait RgbSealSrc: SingleUseSeal> + Ord {} // Below are capabilities constants used in the standard library: diff --git a/src/verify.rs b/src/verify.rs index 00896373..c832767f 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -27,7 +27,6 @@ use alloc::vec::Vec; use amplify::confinement::SmallOrdMap; use amplify::ByteArray; -use bp::seals::mmb; use single_use_seals::{PublishedWitness, SealError, SealWitness}; use ultrasonic::{CallError, CellAddr, Codex, ContractId, LibRepo, Memory, Operation, Opid}; @@ -144,9 +143,9 @@ pub trait ContractVerify: ContractApi { // channel). match witness_reader.read_witness() { Step::Next((witness, w)) => { - let msg = mmb::Message::from_byte_array(opid.to_byte_array()); + let msg = opid.to_byte_array(); witness - .verify_seals_closing(&closed_seals, msg) + .verify_seals_closing(&closed_seals, msg.into()) .map_err(|e| VerificationError::SealsNotClosed(witness.published.pub_id(), opid, e))?; // Each witness actually produces its own set of witness-output based seal sources. From 0cd02a7b300a4234cee9deab4f573c9853b72238 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 30 Jan 2025 12:59:12 +0100 Subject: [PATCH 10/12] account for defined seals for genesis and state extensions --- src/verify.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/verify.rs b/src/verify.rs index c832767f..9aa18fd0 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -137,7 +137,11 @@ pub trait ContractVerify: ContractApi { // This convoluted logic happens since we use a state machine which ensures the client can't lie to // the verifier let mut witness_count = 0usize; - let mut seal_sources = BTreeSet::new(); + let mut seal_sources: BTreeSet<_> = header + .defined_seals + .iter() + .filter_map(|(pos, seal)| seal.to_src().map(|seal| (CellAddr::new(opid, *pos), seal))) + .collect(); loop { // An operation may have multiple witnesses (like multiple commitment transactions in lightning // channel). @@ -152,6 +156,7 @@ pub trait ContractVerify: ContractApi { let iter = header .defined_seals .iter() + .filter(|(_, seal)| seal.to_src().is_none()) .map(|(pos, seal)| (CellAddr::new(opid, *pos), seal.resolve(witness.published.pub_id()))); seal_sources.extend(iter); From f7ba241a7d91bcecac444b5474051751a0997680 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 30 Jan 2025 13:47:15 +0100 Subject: [PATCH 11/12] chore: fix liquid build --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1001d187..aaf5a93a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ default = ["bitcoin"] all = ["bitcoin", "liquid", "prime", "serde"] bitcoin = ["bp-core"] -liquid = [] +liquid = ["bp-core"] prime = [] serde = [ From b818ad10c91106bcf337d35638a16361bf0fda1d Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 30 Jan 2025 13:49:44 +0100 Subject: [PATCH 12/12] add test to make codecov succeed --- src/seals.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/seals.rs b/src/seals.rs index e7fbcdaf..d9724738 100644 --- a/src/seals.rs +++ b/src/seals.rs @@ -85,3 +85,20 @@ pub mod bitcoin { } } } + +#[cfg(test)] +mod tests { + use bp::seals::{TxoSealExt, WOutpoint, WTxoSeal}; + use bp::Outpoint; + + use super::*; + + #[test] + fn auth_token() { + let seal = WTxoSeal { + primary: WOutpoint::Wout(0u32.into()), + secondary: TxoSealExt::Fallback(Outpoint::coinbase()), + }; + assert_eq!(seal.auth_token().to_string(), "at:lIIfSD7P-RQi0r3kA-7gZdmE7Q-S66QSwzG-NCxNnh7V-225u4Q"); + } +}