Skip to content

Commit

Permalink
Implement PSKT(Partially Signed Kaspa Transaction) (kaspanet#481)
Browse files Browse the repository at this point in the history
* initial support of pskt:
    supported roles: creator, constructor, updater, signer roles

* add builder

* handle combine errors

* finalize

* extractor

* chore: typo

* style: fmt

* expose txid to global

* chore: change version

* feat: serde for optional bytes

* feat: impl (de)serialization

* style: fmt

* add example, fixes

* style: fmt

* style: clippy

* rollback unrelated changes

* psbt -> pskt

* refactor: avoid copy-paste by using recursion

* docs: add description of roles
  • Loading branch information
biryukovmaxim authored Jun 18, 2024
1 parent a797e1e commit 6a56461
Show file tree
Hide file tree
Showing 16 changed files with 1,309 additions and 3 deletions.
50 changes: 50 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"wallet/wasm",
"wallet/bip32",
"wallet/keys",
"wallet/pskt",
"consensus",
"consensus/core",
"consensus/client",
Expand Down Expand Up @@ -123,6 +124,7 @@ kaspa-utxoindex = { version = "0.14.1", path = "indexes/utxoindex" }
kaspa-wallet = { version = "0.14.1", path = "wallet/native" }
kaspa-wallet-cli-wasm = { version = "0.14.1", path = "wallet/wasm" }
kaspa-wallet-keys = { version = "0.14.1", path = "wallet/keys" }
kaspa-wallet-pskt = { version = "0.14.1", path = "wallet/pskt" }
kaspa-wallet-core = { version = "0.14.1", path = "wallet/core" }
kaspa-wallet-macros = { version = "0.14.1", path = "wallet/macros" }
kaspa-wasm = { version = "0.14.1", path = "wasm" }
Expand Down Expand Up @@ -162,6 +164,7 @@ ctrlc = "3.4.1"
crypto_box = { version = "0.9.1", features = ["chacha20"] }
dashmap = "5.5.3"
derivative = "2.2.0"
derive_builder = "0.20.0"
derive_more = "0.99.17"
dhat = "0.3.2"
dirs = "5.0.1"
Expand Down Expand Up @@ -228,6 +231,7 @@ serde = { version = "1.0.190", features = ["derive", "rc"] }
serde_bytes = "0.11.12"
serde_json = "1.0.107"
serde_repr = "0.1.18"
serde-value = "0.7.0"
serde-wasm-bindgen = "0.6.1"
sha1 = "0.10.6"
sha2 = "0.10.8"
Expand Down
3 changes: 2 additions & 1 deletion consensus/core/src/hashing/sighash_type.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;

pub const SIG_HASH_ALL: SigHashType = SigHashType(0b00000001);
Expand All @@ -18,7 +19,7 @@ const ALLOWED_SIG_HASH_TYPES_VALUES: [u8; 6] = [
SIG_HASH_SINGLE.0 | SIG_HASH_ANY_ONE_CAN_PAY.0,
];

#[derive(Copy, Clone)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[wasm_bindgen]
pub struct SigHashType(pub(crate) u8);

Expand Down
4 changes: 2 additions & 2 deletions consensus/core/src/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub type TransactionId = kaspa_hashes::Hash;
/// score of the block that accepts the tx, its public key script, and how
/// much it pays.
/// @category Consensus
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[serde(rename_all = "camelCase")]
#[wasm_bindgen(inspectable, js_name = TransactionUtxoEntry)]
pub struct UtxoEntry {
Expand All @@ -53,7 +53,7 @@ impl MemSizeEstimator for UtxoEntry {}
pub type TransactionIndexType = u32;

/// Represents a Kaspa transaction outpoint
#[derive(Eq, Hash, PartialEq, Debug, Copy, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[derive(Eq, Default, Hash, PartialEq, Debug, Copy, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionOutpoint {
#[serde(with = "serde_bytes_fixed_ref")]
Expand Down
1 change: 1 addition & 0 deletions utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub mod as_slice;
/// assert_eq!(test_struct, from_json);
/// ```
pub mod serde_bytes;
pub mod serde_bytes_optional;

/// # Examples
///
Expand Down
111 changes: 111 additions & 0 deletions utils/src/serde_bytes_optional.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
pub use de::Deserialize;
pub use ser::Serialize;

pub fn serialize<T, S>(bytes: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: ?Sized + Serialize,
S: serde::Serializer,
{
Serialize::serialize(bytes, serializer)
}

pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: Deserialize<'de>,
D: serde::Deserializer<'de>,
{
Deserialize::deserialize(deserializer)
}

mod de {
use std::fmt::Display;

pub trait Deserialize<'de>: Sized {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>;
}

impl<'de, T: crate::serde_bytes::Deserialize<'de>> Deserialize<'de> for Option<T>
where
<T as TryFrom<&'de [u8]>>::Error: Display,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct OptionalVisitor<T> {
out: std::marker::PhantomData<T>,
}

impl<'de, T> serde::de::Visitor<'de> for OptionalVisitor<T>
where
T: crate::serde_bytes::Deserialize<'de>,
{
type Value = Option<T>;

fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("optional string, str or slice, vec of bytes")
}

fn visit_unit<E: serde::de::Error>(self) -> Result<Self::Value, E> {
Ok(None)
}

fn visit_none<E: serde::de::Error>(self) -> Result<Self::Value, E> {
Ok(None)
}

fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
T::deserialize(deserializer).map(Some)
}
}

let visitor = OptionalVisitor { out: std::marker::PhantomData };
deserializer.deserialize_option(visitor)
}
}
}

mod ser {
use serde::Serializer;

pub trait Serialize {
#[allow(missing_docs)]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer;
}

impl<T> Serialize for Option<T>
where
T: crate::serde_bytes::Serialize + std::convert::AsRef<[u8]>,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
struct AsBytes<T>(T);

impl<T> serde::Serialize for AsBytes<T>
where
T: crate::serde_bytes::Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
crate::serde_bytes::Serialize::serialize(&self.0, serializer)
}
}

match self {
Some(b) => serializer.serialize_some(&AsBytes(b)),
None => serializer.serialize_none(),
}
}
}
}
40 changes: 40 additions & 0 deletions wallet/bip32/src/derivation_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use core::{
fmt::{self, Display},
str::FromStr,
};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};

/// Prefix for all derivation paths.
const PREFIX: &str = "m";
Expand All @@ -16,6 +17,45 @@ pub struct DerivationPath {
path: Vec<ChildNumber>,
}

impl<'de> Deserialize<'de> for DerivationPath {
fn deserialize<D>(deserializer: D) -> std::result::Result<DerivationPath, D::Error>
where
D: Deserializer<'de>,
{
struct DerivationPathVisitor;
impl<'de> de::Visitor<'de> for DerivationPathVisitor {
type Value = DerivationPath;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a string containing list of permissions separated by a '+'")
}

fn visit_str<E>(self, value: &str) -> std::result::Result<Self::Value, E>
where
E: de::Error,
{
DerivationPath::from_str(value).map_err(|err| de::Error::custom(err.to_string()))
}
fn visit_borrowed_str<E>(self, v: &'de str) -> std::result::Result<Self::Value, E>
where
E: de::Error,
{
DerivationPath::from_str(v).map_err(|err| de::Error::custom(err.to_string()))
}
}

deserializer.deserialize_str(DerivationPathVisitor)
}
}

impl Serialize for DerivationPath {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}

impl DerivationPath {
/// Iterate over the [`ChildNumber`] values in this derivation path.
pub fn iter(&self) -> impl Iterator<Item = ChildNumber> + '_ {
Expand Down
37 changes: 37 additions & 0 deletions wallet/pskt/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[package]
name = "kaspa-wallet-pskt"
keywords = ["kaspa", "wallet", "pskt", "psbt", "bip-370"]
description = "Partially Signed Kaspa Transaction"
categories = ["cryptography::cryptocurrencies"]
rust-version.workspace = true
version.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
edition.workspace = true
include.workspace = true

[lib]
crate-type = ["cdylib", "lib"]

[features]
wasm32-sdk = ["kaspa-consensus-client/wasm32-sdk"]
wasm32-types = ["kaspa-consensus-client/wasm32-types"]

[dependencies]
kaspa-bip32.workspace = true
kaspa-consensus-client.workspace = true
kaspa-consensus-core.workspace = true
kaspa-txscript-errors.workspace = true
kaspa-txscript.workspace = true
kaspa-utils.workspace = true

derive_builder.workspace = true
secp256k1.workspace = true
serde-value.workspace = true
serde.workspace = true
serde_repr.workspace = true
thiserror.workspace = true

[dev-dependencies]
serde_json.workspace = true
Loading

0 comments on commit 6a56461

Please sign in to comment.