Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 80 additions & 11 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:
sudo apt -y autoclean
sudo apt clean
rm --recursive --force "$AGENT_TOOLSDIRECTORY"
df -h
df -h

# remove large packages manually (all but llvm)
sudo apt-get remove -y '^aspnetcore-.*' || echo "::warning::The command [sudo apt-get remove -y '^aspnetcore-.*'] failed to complete successfully. Proceeding..."
Expand All @@ -65,7 +65,7 @@ jobs:
sudo apt-get remove -y google-cloud-cli --fix-missing || echo "::debug::The command [sudo apt-get remove -y google-cloud-cli --fix-missing] failed to complete successfully. Proceeding..."
sudo apt-get autoremove -y || echo "::warning::The command [sudo apt-get autoremove -y] failed to complete successfully. Proceeding..."
sudo apt-get clean || echo "::warning::The command [sudo apt-get clean] failed to complete successfully. Proceeding..."
df -h
df -h

# Free up disk space on Ubuntu
- name: Free Disk Space (Ubuntu)
Expand Down Expand Up @@ -95,10 +95,10 @@ jobs:

- name: Install stable toolchain
uses: dtolnay/rust-toolchain@stable

- name: Set up cache
uses: Swatinem/rust-cache@v2

- name: Install cargo-nextest
run: cargo install cargo-nextest

Expand Down Expand Up @@ -133,7 +133,6 @@ jobs:
- name: Run kip-10 example
run: cargo run --example kip-10


# test-release:
# name: Test Suite Release
# runs-on: ${{ matrix.os }}
Expand Down Expand Up @@ -217,7 +216,6 @@ jobs:
- name: Run cargo clippy
run: cargo clippy --workspace --tests --benches --examples -- -D warnings


check-wasm32:
name: Check WASM32
runs-on: ubuntu-latest
Expand Down Expand Up @@ -281,6 +279,78 @@ jobs:
- name: Run cargo check of kaspa-wasm for wasm32 target
run: cargo clippy -p kaspa-wasm --target wasm32-unknown-unknown

test-wasm32:
name: Test WASM32
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4

- name: Install Protoc
uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Install llvm
id: install_llvm
continue-on-error: true
run: |
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
sudo apt-get install -y clang-15 lldb-15 lld-15 clangd-15 clang-tidy-15 clang-format-15 clang-tools-15 llvm-15-dev lld-15 lldb-15 llvm-15-tools libomp-15-dev libc++-15-dev libc++abi-15-dev libclang-common-15-dev libclang-15-dev libclang-cpp15-dev libunwind-15-dev
# Make Clang 15 default
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/lib/llvm-15/bin/clang++ 100
sudo update-alternatives --install /usr/bin/clang clang /usr/lib/llvm-15/bin/clang 100
sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/lib/llvm-15/bin/clang-format 100
sudo update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/lib/llvm-15/bin/clang-tidy 100
sudo update-alternatives --install /usr/bin/run-clang-tidy run-clang-tidy /usr/lib/llvm-15/bin/run-clang-tidy 100
# Alias cc to clang
sudo update-alternatives --install /usr/bin/cc cc /usr/lib/llvm-15/bin/clang 0
sudo update-alternatives --install /usr/bin/c++ c++ /usr/lib/llvm-15/bin/clang++ 0

- name: Install gcc-multilib
# gcc-multilib allows clang to find gnu libraries properly
run: |
sudo apt-get update
sudo apt install -y gcc-multilib

- name: Install stable toolchain
if: steps.install_llvm.outcome == 'success' && steps.install_llvm.conclusion == 'success'
uses: dtolnay/rust-toolchain@stable

- name: Add wasm32 target
run: rustup target add wasm32-unknown-unknown

- name: Install wasm-pack
run: cargo install wasm-pack

- name: Cache
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}

# test runner is Node.js
- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version: 24

# append here any new crates that contains wasm test suites (and aren't already covered)

- name: Run wasm tests for consensus/core
run: wasm-pack test --node consensus/core

- name: Run wasm tests for crypto/addresses
run: wasm-pack test --node crypto/addresses

- name: Run wasm tests for wallet/pskt
run: wasm-pack test --node wallet/pskt

build-wasm32:
name: Build WASM32 SDK
runs-on: ubuntu-latest
Expand Down Expand Up @@ -332,7 +402,7 @@ jobs:
- name: Install NodeJS
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: "20"

- name: Install NodeJS dependencies
run: npm install --global typedoc typescript
Expand All @@ -355,9 +425,9 @@ jobs:
bash build-release
popd

- name: Upload WASM build to GitHub
- name: Upload WASM build to GitHub
uses: actions/upload-artifact@v4
with:
with:
name: kaspa-wasm32-sdk-${{ env.SHORT_SHA }}
path: wasm/release/
build-release:
Expand Down Expand Up @@ -387,7 +457,7 @@ jobs:
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-

- name: Cache Toolchain
uses: actions/cache@v4
with:
Expand All @@ -397,7 +467,6 @@ jobs:
restore-keys: |
${{ runner.os }}-musl-


- name: Build RK with musl toolchain
if: runner.os == 'Linux'
run: |
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions wallet/pskt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ kaspa-consensus-core.workspace = true
kaspa-txscript-errors.workspace = true
kaspa-txscript.workspace = true
kaspa-utils.workspace = true
kaspa-wallet-keys.workspace = true

bincode.workspace = true
derive_builder.workspace = true
Expand Down
4 changes: 3 additions & 1 deletion wallet/pskt/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@

use kaspa_txscript_errors::TxScriptError;

use crate::input::InputBuilderError;
use crate::{input::InputBuilderError, pskt::ExtractError};

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("{0}")]
Custom(String),
#[error(transparent)]
ConstructorError(#[from] ConstructorError),
#[error(transparent)]
ExtractError(#[from] ExtractError),
#[error("OutputNotModifiable")]
OutOfBounds,
#[error("Missing UTXO entry")]
Expand Down
60 changes: 57 additions & 3 deletions wallet/pskt/src/wasm/bundle.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
use std::collections::HashSet;
use std::ops::Deref;

use super::error::*;
use super::result::*;
use crate::bundle::Bundle as Inner;
use crate::pskt::Inner as PSKTInner;
use crate::wasm::pskt::*;
use crate::wasm::signer::PrivateKeyArrayT;
use crate::wasm::utils::sompi_to_kaspa_string_with_suffix;
use kaspa_consensus_core::network::{NetworkId, NetworkIdT};
use kaspa_addresses::Address;
use kaspa_consensus_client::Transaction;
use kaspa_consensus_core::network::{NetworkId, NetworkIdT, NetworkTypeT};
use wasm_bindgen::prelude::*;
use workflow_wasm::convert::TryCastFromJs;

Expand Down Expand Up @@ -50,6 +54,15 @@ impl PSKB {
self.0 .0.len()
}

#[wasm_bindgen(js_name = "get")]
pub fn get(&self, index: usize) -> Result<PSKT> {
let inner: &PSKTInner = self.0 .0.get(index).ok_or_else(|| Error::Custom(format!("Index out of bounds: {index}")))?;

let inner_clone: PSKTInner = inner.clone();

Ok(PSKT::from(State::NoOp(Some(inner_clone))))
}

pub fn add(&mut self, pskt: &PSKT) -> Result<()> {
let payload = pskt.payload_getter();

Expand Down Expand Up @@ -97,6 +110,45 @@ impl PSKB {
pub fn merge(&mut self, other: &PSKB) {
self.0.merge(other.clone().0);
}

/// Extracts all unique input addresses from all PSKTs in the bundle.
/// This is useful for figuring out which private keys are required for signing.
#[wasm_bindgen]
pub fn addresses(&self, network_id: &NetworkIdT) -> Result<Vec<Address>> {
let mut addresses = HashSet::with_capacity(self.length());

for i in 0..self.length() {
let pskt = self.get(i)?;
let pskt_addresses = pskt.addresses(network_id)?;
addresses.extend(pskt_addresses);
}

Ok(Vec::from_iter(addresses))
}

#[wasm_bindgen]
pub fn sign(&self, private_keys: PrivateKeyArrayT, network_type: &NetworkTypeT) -> Result<PSKB> {
let mut new_bundle_inner = Inner::new();
for i in 0..self.length() {
let pskt_wasm = self.get(i)?;
let signed_pskt_wasm = pskt_wasm.sign(private_keys.clone(), network_type)?;
let inner_pskt = signed_pskt_wasm.inner()?;
new_bundle_inner.add_inner(inner_pskt);
}

Ok(PSKB(new_bundle_inner))
}

#[wasm_bindgen(js_name = "finalizeP2PK")]
pub fn finalize_p2pk(&self, network_type: &NetworkTypeT) -> Result<Vec<Transaction>> {
let mut transactions = Vec::with_capacity(self.length());
for i in 0..self.length() {
let pskt = self.get(i)?;
let transaction = pskt.finalize_p2pk(network_type)?;
transactions.push(transaction);
}
Ok(transactions)
}
}

impl From<Inner> for PSKB {
Expand Down Expand Up @@ -166,7 +218,8 @@ mod tests {
"outputCount": 0,
"xpubs": {},
"id": null,
"proprietaries": {}
"proprietaries": {},
"payload": "",
},
"inputs": [
{
Expand Down Expand Up @@ -225,7 +278,7 @@ mod tests {

#[wasm_bindgen_test]
fn _test_deser() {
let pskb = "PSKB5b7b22676c6f62616c223a7b2276657273696f6e223a302c22747856657273696f6e223a302c2266616c6c6261636b4c6f636b54696d65223a6e756c6c2c22696e707574734d6f6469666961626c65223a66616c73652c226f7574707574734d6f6469666961626c65223a66616c73652c22696e707574436f756e74223a302c226f7574707574436f756e74223a302c227870756273223a7b7d2c226964223a6e756c6c2c2270726f70726965746172696573223a7b7d7d2c22696e70757473223a5b7b227574786f456e747279223a7b22616d6f756e74223a3436383932383838372c227363726970745075626c69634b6579223a22303030303230326438613134313465363265303831666236626366363434653634386331383036316332383535353735636163373232663836333234636164393164643066616163222c22626c6f636b44616153636f7265223a38343938313138362c226973436f696e62617365223a66616c73657d2c2270726576696f75734f7574706f696e74223a7b227472616e73616374696f6e4964223a2236393135356430653333383065383831366466666532363731323934616431303466306233373736663335626365316132326630633231623166393038353030222c22696e646578223a307d2c2273657175656e6365223a6e756c6c2c226d696e54696d65223a6e756c6c2c227061727469616c53696773223a7b7d2c227369676861736854797065223a312c2272656465656d536372697074223a6e756c6c2c227369674f70436f756e74223a312c22626970333244657269766174696f6e73223a7b7d2c2266696e616c536372697074536967223a6e756c6c2c2270726f70726965746172696573223a7b7d7d5d2c226f757470757473223a5b7b22616d6f756e74223a313530303030303030302c227363726970745075626c69634b6579223a2230303030222c2272656465656d536372697074223a6e756c6c2c22626970333244657269766174696f6e73223a7b7d2c2270726f70726965746172696573223a7b7d7d5d7d5d";
let pskb = "PSKB5b7b22676c6f62616c223a7b2276657273696f6e223a302c22747856657273696f6e223a302c2266616c6c6261636b4c6f636b54696d65223a6e756c6c2c22696e707574734d6f6469666961626c65223a66616c73652c226f7574707574734d6f6469666961626c65223a66616c73652c22696e707574436f756e74223a302c226f7574707574436f756e74223a302c227870756273223a7b7d2c226964223a6e756c6c2c2270726f70726965746172696573223a7b7d2c227061796c6f6164223a22227d2c22696e70757473223a5b7b227574786f456e747279223a7b22616d6f756e74223a3436383932383838372c227363726970745075626c69634b6579223a22303030303230326438613134313465363265303831666236626366363434653634386331383036316332383535353735636163373232663836333234636164393164643066616163222c22626c6f636b44616153636f7265223a38343938313138362c226973436f696e62617365223a66616c73657d2c2270726576696f75734f7574706f696e74223a7b227472616e73616374696f6e4964223a2236393135356430653333383065383831366466666532363731323934616431303466306233373736663335626365316132326630633231623166393038353030222c22696e646578223a307d2c2273657175656e6365223a6e756c6c2c226d696e54696d65223a6e756c6c2c227061727469616c53696773223a7b7d2c227369676861736854797065223a312c2272656465656d536372697074223a6e756c6c2c227369674f70436f756e74223a312c22626970333244657269766174696f6e73223a7b7d2c2266696e616c536372697074536967223a6e756c6c2c2270726f70726965746172696573223a7b7d7d5d2c226f757470757473223a5b7b22616d6f756e74223a313530303030303030302c227363726970745075626c69634b6579223a2230303030222c2272656465656d536372697074223a6e756c6c2c22626970333244657269766174696f6e73223a7b7d2c2270726f70726965746172696573223a7b7d7d5d7d5d";
// Deserialize the bundle
let deserialized_bundle = PSKB::deserialize(pskb).expect("Failed to deserialize bundle");
assert_eq!(deserialized_bundle.length(), 1, "Should be length 1");
Expand All @@ -237,5 +290,6 @@ mod tests {
inner.outputs.first().expect("output").script_public_key,
ScriptPublicKey::from_str("0000").expect("convert valid spk")
);
assert_eq!(inner.global.payload, Some("".into()));
}
}
10 changes: 10 additions & 0 deletions wallet/pskt/src/wasm/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::pskt::State;
use kaspa_txscript::script_class::ScriptClass;
use thiserror::Error;
use wasm_bindgen::prelude::*;

Expand Down Expand Up @@ -31,11 +32,20 @@ pub enum Error {
#[error("PSKT must be initialized with a payload or CREATE role")]
NotInitialized,

#[error("Invalid ScriptClass, expected {0}, got {1}")]
UnexpectedScriptClassError(ScriptClass, ScriptClass),

#[error(transparent)]
ConsensusClient(#[from] kaspa_consensus_client::error::Error),

#[error(transparent)]
Pskt(#[from] crate::error::Error),

#[error(transparent)]
NetworkIdError(#[from] kaspa_consensus_core::network::NetworkIdError),

#[error(transparent)]
NetworkTypeError(#[from] kaspa_consensus_core::network::NetworkTypeError),
}

impl Error {
Expand Down
1 change: 1 addition & 0 deletions wallet/pskt/src/wasm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ pub mod input;
pub mod output;
pub mod pskt;
pub mod result;
pub mod signer;
pub mod utils;
Loading