diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f7e492..839c39b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,7 @@ jobs: rustup component add rustfmt cargo install wasm-pack --version 0.13.1 cargo install wasm-opt --version 0.116.1 + cargo install cargo-deny --locked - name: Build Info run: | @@ -54,6 +55,7 @@ jobs: echo "rustc $(rustc --version)" echo "wasm-pack $(wasm-pack --version)" echo "wasm-opt $(wasm-opt --version)" + echo "cargo-deny $(cargo deny --version)" git --version echo "base ref $GITHUB_BASE_REF" echo "head ref $GITHUB_HEAD_REF" @@ -65,6 +67,10 @@ jobs: - name: Install Packages run: npm ci --workspaces --include-workspace-root + - name: Check dependencies with cargo-deny + run: cargo deny check + working-directory: packages/wasm-utxo + - name: test run: npx --version diff --git a/packages/wasm-utxo/Cargo.lock b/packages/wasm-utxo/Cargo.lock index efea235..4e541b1 100644 --- a/packages/wasm-utxo/Cargo.lock +++ b/packages/wasm-utxo/Cargo.lock @@ -128,10 +128,11 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -161,15 +162,6 @@ name = "once_cell" version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" -dependencies = [ - "portable-atomic", -] - -[[package]] -name = "portable-atomic" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "proc-macro2" @@ -189,6 +181,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" version = "1.0.20" @@ -271,24 +269,25 @@ checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -297,9 +296,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -307,9 +306,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", @@ -320,15 +319,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-utxo" version = "0.1.0" dependencies = [ "base64", + "bech32", "hex", "js-sys", "miniscript", diff --git a/packages/wasm-utxo/Cargo.toml b/packages/wasm-utxo/Cargo.toml index e7f228b..6c58a15 100644 --- a/packages/wasm-utxo/Cargo.toml +++ b/packages/wasm-utxo/Cargo.toml @@ -10,6 +10,7 @@ crate-type = ["cdylib"] wasm-bindgen = "0.2" js-sys = "0.3" miniscript = { git = "https://github.com/BitGo/rust-miniscript", tag = "miniscript-12.3.4-opdrop" } +bech32 = "0.11" [dev-dependencies] base64 = "0.22.1" diff --git a/packages/wasm-utxo/Makefile b/packages/wasm-utxo/Makefile index 412bc41..e9cdfc5 100644 --- a/packages/wasm-utxo/Makefile +++ b/packages/wasm-utxo/Makefile @@ -12,7 +12,7 @@ endef # run wasm-opt separately so we can pass `--enable-bulk-memory` define WASM_OPT_COMMAND - $(WASM_OPT) --enable-bulk-memory -Oz $(1)/*.wasm -o $(1)/*.wasm + $(WASM_OPT) --enable-bulk-memory --enable-nontrapping-float-to-int -Oz $(1)/*.wasm -o $(1)/*.wasm endef define REMOVE_GITIGNORE diff --git a/packages/wasm-utxo/deny.toml b/packages/wasm-utxo/deny.toml new file mode 100644 index 0000000..dd653e9 --- /dev/null +++ b/packages/wasm-utxo/deny.toml @@ -0,0 +1,18 @@ +# Deny multiple versions of dependencies +[bans] +multiple-versions = "deny" +# Highlight bech32 specifically (ensures it's caught if duplicated) +highlight = "all" + +# Allow git sources (needed for miniscript) +[sources] +allow-git = ["https://github.com/BitGo/rust-miniscript"] + +# Allow common licenses used in the Rust ecosystem +[licenses] +allow = ["MIT", "Apache-2.0", "CC0-1.0", "MITNFA", "Unicode-DFS-2016"] +# Clarify license for unlicensed crate +[[licenses.clarify]] +name = "wasm-utxo" +expression = "MIT OR Apache-2.0" +license-files = [] diff --git a/packages/wasm-utxo/js/index.ts b/packages/wasm-utxo/js/index.ts index 4e80ee6..fcda9bf 100644 --- a/packages/wasm-utxo/js/index.ts +++ b/packages/wasm-utxo/js/index.ts @@ -39,8 +39,14 @@ declare module "./wasm/wasm_utxo" { } } +import { Address as WasmAddress } from "./wasm/wasm_utxo"; + export { WrapDescriptor as Descriptor } from "./wasm/wasm_utxo"; export { WrapMiniscript as Miniscript } from "./wasm/wasm_utxo"; export { WrapPsbt as Psbt } from "./wasm/wasm_utxo"; +export namespace utxolibCompat { + export const Address = WasmAddress; +} + export * as ast from "./ast"; diff --git a/packages/wasm-utxo/src/address/base58check.rs b/packages/wasm-utxo/src/address/base58check.rs new file mode 100644 index 0000000..f3af94b --- /dev/null +++ b/packages/wasm-utxo/src/address/base58check.rs @@ -0,0 +1,129 @@ +//! Base58Check encoding/decoding for traditional Bitcoin addresses (P2PKH, P2SH). + +use super::{AddressCodec, AddressError, Result}; +use crate::bitcoin::hashes::Hash; +use crate::bitcoin::{base58, PubkeyHash, Script, ScriptBuf, ScriptHash}; + +/// Base58Check codec with network-specific version bytes +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Base58CheckCodec { + /// Base58Check P2PKH version byte(s) + pub pub_key_hash: u32, + /// Base58Check P2SH version byte(s) + pub script_hash: u32, +} + +impl Base58CheckCodec { + /// Create a new Base58Check codec with specified version bytes + pub const fn new(pub_key_hash: u32, script_hash: u32) -> Self { + Self { + pub_key_hash, + script_hash, + } + } +} + +/// Encode a hash with version bytes to Base58Check format using bitcoin crate +fn to_base58_check(hash: &[u8], version: u32) -> Result { + let mut data = Vec::new(); + + // Encode version bytes (1-4 bytes depending on size) + if version <= 0xff { + data.push(version as u8); + } else if version <= 0xffff { + data.extend_from_slice(&(version as u16).to_be_bytes()); + } else { + // For Zcash (up to 4 bytes) + let bytes = version.to_be_bytes(); + let start = bytes.iter().position(|&b| b != 0).unwrap_or(0); + data.extend_from_slice(&bytes[start..]); + } + + data.extend_from_slice(hash); + + // Use bitcoin crate's base58 encode_check which adds the checksum + Ok(base58::encode_check(&data)) +} + +/// Decode a Base58Check address to (hash, version) using bitcoin crate +fn from_base58_check(address: &str) -> Result<(Vec, u32)> { + // Use bitcoin crate's base58 decode_check which verifies the checksum + let payload = + base58::decode_check(address).map_err(|e| AddressError::Base58Error(e.to_string()))?; + + if payload.is_empty() { + return Err(AddressError::Base58Error("Empty payload".to_string())); + } + + // Extract version and hash + // Try different version byte lengths + let (version, hash) = if payload.len() >= 21 && (payload[0] == 0x1c || payload[0] == 0x1d) { + // Zcash uses 2-byte versions starting with 0x1c or 0x1d + if payload.len() >= 22 { + let version = u32::from_be_bytes([0, 0, payload[0], payload[1]]); + let hash = payload[2..].to_vec(); + (version, hash) + } else { + // Single byte version + let version = payload[0] as u32; + let hash = payload[1..].to_vec(); + (version, hash) + } + } else { + // Standard single-byte version + let version = payload[0] as u32; + let hash = payload[1..].to_vec(); + (version, hash) + }; + + Ok((hash, version)) +} + +impl AddressCodec for Base58CheckCodec { + fn encode(&self, script: &Script) -> Result { + if script.is_p2pkh() { + if script.len() != 25 { + return Err(AddressError::InvalidScript( + "Invalid P2PKH script length".to_string(), + )); + } + let hash = &script.as_bytes()[3..23]; + to_base58_check(hash, self.pub_key_hash) + } else if script.is_p2sh() { + if script.len() != 23 { + return Err(AddressError::InvalidScript( + "Invalid P2SH script length".to_string(), + )); + } + let hash = &script.as_bytes()[2..22]; + to_base58_check(hash, self.script_hash) + } else { + Err(AddressError::UnsupportedScriptType( + "Base58Check only supports P2PKH and P2SH".to_string(), + )) + } + } + + fn decode(&self, address: &str) -> Result { + let (hash, version) = from_base58_check(address)?; + + if version == self.pub_key_hash { + let hash_array: [u8; 20] = hash.try_into().map_err(|_| { + AddressError::InvalidAddress("Invalid pubkey hash length".to_string()) + })?; + let pubkey_hash = PubkeyHash::from_byte_array(hash_array); + Ok(ScriptBuf::new_p2pkh(&pubkey_hash)) + } else if version == self.script_hash { + let hash_array: [u8; 20] = hash.try_into().map_err(|_| { + AddressError::InvalidAddress("Invalid script hash length".to_string()) + })?; + let script_hash = ScriptHash::from_byte_array(hash_array); + Ok(ScriptBuf::new_p2sh(&script_hash)) + } else { + Err(AddressError::InvalidAddress(format!( + "Version mismatch: expected {} or {}, got {}", + self.pub_key_hash, self.script_hash, version + ))) + } + } +} diff --git a/packages/wasm-utxo/src/address/bech32.rs b/packages/wasm-utxo/src/address/bech32.rs new file mode 100644 index 0000000..0431f88 --- /dev/null +++ b/packages/wasm-utxo/src/address/bech32.rs @@ -0,0 +1,134 @@ +//! Bech32 and Bech32m encoding/decoding for Bitcoin witness addresses. +//! +//! Implements BIP 173 (Bech32) and BIP 350 (Bech32m) encoding schemes using the bitcoin crate. +//! - Bech32 is used for witness version 0 (P2WPKH, P2WSH) +//! - Bech32m is used for witness version 1+ (P2TR) + +use super::{AddressCodec, AddressError, Result}; +use crate::bitcoin::{Script, ScriptBuf, WitnessVersion}; + +/// Bech32/Bech32m codec for witness addresses +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Bech32Codec { + /// Bech32 Human Readable Part (HRP) + pub hrp: &'static str, +} + +impl Bech32Codec { + /// Create a new Bech32 codec with the specified HRP + pub const fn new(hrp: &'static str) -> Self { + Self { hrp } + } +} + +/// Encode witness program with custom HRP +pub fn encode_witness_with_custom_hrp( + program: &[u8], + version: WitnessVersion, + hrp_str: &str, +) -> Result { + // Try using the bech32 functionality from bitcoin crate + // The bitcoin crate includes bech32 encoding via its dependencies + use bech32::{self, Hrp}; + + // Parse the HRP + let hrp = Hrp::parse(hrp_str) + .map_err(|e| AddressError::Bech32Error(format!("Invalid HRP '{}': {}", hrp_str, e)))?; + + // Encode based on witness version + let address = if version == WitnessVersion::V0 { + // Use Bech32 for witness version 0 + bech32::segwit::encode_v0(hrp, program) + .map_err(|e| AddressError::Bech32Error(format!("Bech32 encoding failed: {}", e)))? + } else { + // Use Bech32m for witness version 1+ + bech32::segwit::encode_v1(hrp, program) + .map_err(|e| AddressError::Bech32Error(format!("Bech32m encoding failed: {}", e)))? + }; + + Ok(address) +} + +/// Extract witness version and program from a script +pub fn extract_witness_program(script: &Script) -> Result<(WitnessVersion, &[u8])> { + if script.is_p2wpkh() { + if script.len() != 22 { + return Err(AddressError::InvalidScript( + "Invalid P2WPKH script length".to_string(), + )); + } + Ok((WitnessVersion::V0, &script.as_bytes()[2..22])) + } else if script.is_p2wsh() { + if script.len() != 34 { + return Err(AddressError::InvalidScript( + "Invalid P2WSH script length".to_string(), + )); + } + Ok((WitnessVersion::V0, &script.as_bytes()[2..34])) + } else if script.is_p2tr() { + if script.len() != 34 { + return Err(AddressError::InvalidScript( + "Invalid P2TR script length".to_string(), + )); + } + Ok((WitnessVersion::V1, &script.as_bytes()[2..34])) + } else { + Err(AddressError::UnsupportedScriptType( + "Bech32 only supports witness programs (P2WPKH, P2WSH, P2TR)".to_string(), + )) + } +} + +/// Decode witness program with custom HRP +pub fn decode_witness_with_custom_hrp(address: &str, expected_hrp: &str) -> Result> { + use bech32::{self, Hrp}; + + // Parse the expected HRP + let expected_hrp_parsed = Hrp::parse(expected_hrp) + .map_err(|e| AddressError::Bech32Error(format!("Invalid HRP '{}': {}", expected_hrp, e)))?; + + // Decode the address + let (decoded_hrp, witness_version, witness_program) = bech32::segwit::decode(address) + .map_err(|e| AddressError::Bech32Error(format!("Failed to decode address: {}", e)))?; + + // Verify HRP matches + if decoded_hrp != expected_hrp_parsed { + return Err(AddressError::Bech32Error(format!( + "HRP mismatch: expected '{}', got '{}'", + expected_hrp, decoded_hrp + ))); + } + + // Convert witness version (Fe32) to OP code + // Fe32 can be 0-31, but for segwit, we only care about 0-16 + // OP_0 = 0x00, OP_1 = 0x51, OP_2 = 0x52, ... OP_16 = 0x60 + let version_byte: u8 = witness_version.to_u8(); + let version_opcode = if version_byte == 0 { + 0x00 // OP_0 + } else if version_byte <= 16 { + 0x50 + version_byte // OP_1 through OP_16 + } else { + return Err(AddressError::Bech32Error(format!( + "Invalid witness version: {}", + version_byte + ))); + }; + + // Construct the script pubkey: + let mut script = vec![version_opcode, witness_program.len() as u8]; + script.extend_from_slice(&witness_program); + Ok(script) +} + +impl AddressCodec for Bech32Codec { + fn encode(&self, script: &Script) -> Result { + let (witness_version, program) = extract_witness_program(script)?; + encode_witness_with_custom_hrp(program, witness_version, self.hrp) + } + + fn decode(&self, address: &str) -> Result { + // Use custom HRP decoding for all networks + let script_bytes = decode_witness_with_custom_hrp(address, self.hrp)?; + Ok(ScriptBuf::from(script_bytes)) + } +} diff --git a/packages/wasm-utxo/src/address/cashaddr.rs b/packages/wasm-utxo/src/address/cashaddr.rs new file mode 100644 index 0000000..4865339 --- /dev/null +++ b/packages/wasm-utxo/src/address/cashaddr.rs @@ -0,0 +1,668 @@ +//! Cashaddr encoding/decoding module for Bitcoin Cash and eCash. +//! +//! Implements the cashaddr checksum algorithm as defined in: +//! - Spec: https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md +//! - Reference implementation: https://github.com/Bitcoin-ABC/bitcoin-abc/blob/master/src/cashaddr.cpp +//! +//! This implementation directly follows the official specification and passes all test vectors. +//! +//! # CashAddr vs Bech32 Differences +//! +//! While both CashAddr and Bech32 use 5-bit encoding with checksums, they are fundamentally +//! different encoding schemes and are **not compatible**: +//! +//! ## 1. Checksum Algorithm +//! - **Bech32/Bech32m**: Uses BCH codes with specific generator polynomial (6 checksum characters) +//! - **CashAddr**: Uses different polymod algorithm with 5 generators (8 checksum characters) +//! - Generators: `[0x98f2bc8e61, 0x79b76d99e2, 0xf33e5fb3c4, 0xae2eabe2a8, 0x1e4f43e470]` +//! +//! ## 2. Prefix Handling +//! - **Bech32**: HRP is expanded using both upper 3 bits and lower 5 bits of each character +//! - Format: `[c >> 5 for c in hrp] + [0] + [c & 31 for c in hrp]` +//! - **CashAddr**: Prefix uses only lower 5 bits of each character +//! - Format: `[c & 31 for c in prefix] + [0]` +//! +//! ## 3. Address Format +//! - **Bech32**: `hrp1` (separator is always '1') +//! - **CashAddr**: `prefix:` (separator is ':' and prefix is optional) +//! +//! ## 4. Version/Type Encoding +//! - **Bech32**: First character encodes witness version (0-16) +//! - **CashAddr**: First byte encodes both type (P2PKH/P2SH) and size +//! - Bit 3: 0 = P2PKH, 1 = P2SH +//! - Bits 0-2: Payload size (0 = 20 bytes, 1 = 24 bytes, etc.) +//! +//! ## 5. Padding Validation +//! - **Bech32**: More lenient with padding bits +//! - **CashAddr**: Strict validation - non-zero padding in remaining bits is an error +//! +//! ## Why We Use Only `Fe32` from the bech32 Crate +//! +//! The `Fe32` type from the bech32 crate is a general 5-bit field element primitive that works +//! for any base32-like encoding. However, the higher-level utilities in the bech32 crate +//! (`ByteIterExt`, `Fe32IterExt`, checksum functions, etc.) are specifically designed for +//! Bech32/Bech32m and would produce incorrect results for CashAddr. +//! +//! Therefore, this module: +//! - ✓ Uses `Fe32` for character/byte conversions (`.to_char()`, `.from_char()`, `.to_u8()`) +//! - ✗ Implements its own bit packing/unpacking to handle CashAddr's specific padding rules +//! - ✗ Implements its own polymod checksum function with CashAddr's generators +//! - ✗ Implements its own prefix expansion matching CashAddr's specification +//! +//! ## Quick Reference: CashAddr vs Bech32 +//! +//! | Feature | Bech32/Bech32m | CashAddr | +//! |---------|----------------|----------| +//! | **Separator** | `1` | `:` (optional) | +//! | **Example** | `bc1qw508...` | `bitcoincash:qpm2q...` | +//! | **Checksum Length** | 6 characters (30 bits) | 8 characters (40 bits) | +//! | **Checksum Algorithm** | BCH codes | Custom polymod (5 generators) | +//! | **Prefix Expansion** | `[b>>5...] + [0] + [b&31...]` | `[b&31...] + [0]` | +//! | **Version Encoding** | First char = witness version | First byte = type + size | +//! | **Padding Validation** | Lenient | Strict (must be zero) | +//! | **Used By** | Bitcoin SegWit | Bitcoin Cash, eCash | +//! | **Compatible?** | ❌ No - completely different algorithms | + +use super::{AddressCodec, AddressError, Result}; +use crate::bitcoin::hashes::Hash; +use crate::bitcoin::{PubkeyHash, Script, ScriptBuf, ScriptHash}; +use bech32::Fe32; + +/// CashAddr codec for Bitcoin Cash and eCash +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct CashAddrCodec { + /// Cashaddr prefix (e.g., "bitcoincash", "ecash") + pub prefix: &'static str, + /// P2PKH type identifier for cashaddr (always 0x00) + pub pub_key_hash_type: u8, + /// P2SH type identifier for cashaddr (always 0x08) + pub script_hash_type: u8, +} + +impl CashAddrCodec { + /// Create a new CashAddr codec + pub const fn new(prefix: &'static str, pub_key_hash_type: u8, script_hash_type: u8) -> Self { + Self { + prefix, + pub_key_hash_type, + script_hash_type, + } + } +} + +/// Convert 8-bit bytes to 5-bit Fe32 field elements. +/// +/// This is similar to bech32's `ByteIterExt::bytes_to_fes()` but with CashAddr-specific +/// padding behavior. CashAddr requires that any remaining bits after conversion are +/// padded to create a final 5-bit value (unlike some other schemes). +/// +/// # Algorithm +/// - Accumulates bits from input bytes (8 bits at a time) +/// - Extracts 5-bit values as they become available +/// - Pads remaining bits (if any) by left-shifting to fill a 5-bit value +/// +/// # Example +/// Input: `[0xFF, 0x01]` (16 bits) +/// - First 5 bits: 11111 = 31 +/// - Next 5 bits: 10000 = 16 +/// - Next 5 bits: 00001 = 1 +/// - Remaining 1 bit (1) padded: 10000 = 16 +fn bytes_to_fes(data: &[u8]) -> Result> { + let mut acc: u32 = 0; + let mut bits: u8 = 0; + let mut result = Vec::new(); + + for &byte in data { + acc = (acc << 8) | (byte as u32); + bits += 8; + + while bits >= 5 { + bits -= 5; + let value = ((acc >> bits) & 0x1f) as u8; + result.push( + Fe32::try_from(value) + .map_err(|_| AddressError::CashaddrError("Invalid 5-bit value".to_string()))?, + ); + } + } + + if bits > 0 { + let value = ((acc << (5 - bits)) & 0x1f) as u8; + result.push( + Fe32::try_from(value) + .map_err(|_| AddressError::CashaddrError("Invalid 5-bit value".to_string()))?, + ); + } + + Ok(result) +} + +/// Convert 5-bit Fe32 field elements to 8-bit bytes. +/// +/// This is similar to bech32's `Fe32IterExt::fes_to_bytes()` but with **strict padding +/// validation** specific to CashAddr. This is a key difference from Bech32. +/// +/// # CashAddr Padding Rules (Stricter than Bech32) +/// After converting all 5-bit values to 8-bit bytes, there may be leftover bits. +/// CashAddr requires: +/// 1. Remaining bits must be < 5 (not enough to form another 5-bit value) +/// 2. If there are remaining bits (1-4 bits), they MUST all be zero +/// +/// Bech32 is more lenient with padding, but CashAddr strictly rejects non-zero padding +/// as per the specification to prevent address malleability. +/// +/// # Example +/// Valid: `[31, 16, 1, 16]` with last bits = 0000 (zero padding) +/// Invalid: `[31, 16, 1, 17]` with last bits = 0001 (non-zero padding) ❌ +fn fes_to_bytes(fes: &[Fe32]) -> Result> { + let mut acc: u32 = 0; + let mut bits: u8 = 0; + let mut result = Vec::new(); + + for &fe in fes { + acc = (acc << 5) | (fe.to_u8() as u32); + bits += 5; + + while bits >= 8 { + bits -= 8; + result.push(((acc >> bits) & 0xff) as u8); + } + } + + // CASHADDR-SPECIFIC: Strict padding validation (stricter than Bech32) + // Reject if we have >= 5 bits remaining (should never happen with valid input) + // OR if we have 1-4 bits remaining that are non-zero + if bits >= 5 || (bits > 0 && ((acc << (8 - bits)) & 0xff) != 0) { + return Err(AddressError::CashaddrError( + "Invalid bit conversion".to_string(), + )); + } + + Ok(result) +} + +/// Expand the cashaddr prefix for checksum calculation. +/// +/// # Key Difference from Bech32 +/// +/// **Bech32 HRP Expansion:** +/// ```text +/// hrp = "bc" +/// expanded = [b >> 5 for b in hrp] + [0] + [b & 31 for b in hrp] +/// = [3, 3] + [0] + [2, 3] +/// = [3, 3, 0, 2, 3] +/// ``` +/// +/// **CashAddr Prefix Expansion (This Function):** +/// ```text +/// prefix = "bitcoincash" +/// expanded = [b & 31 for b in prefix] + [0] +/// = [2, 9, 20, 3, 15, 9, 14, 3, 1, 19, 8] + [0] +/// ``` +/// +/// CashAddr only uses the **lower 5 bits** of each character, making it simpler +/// but incompatible with Bech32's two-part expansion. +/// +/// Reference: https://github.com/Bitcoin-ABC/bitcoin-abc/blob/master/src/cashaddr.cpp +fn expand_prefix(prefix: &str) -> Vec { + let mut result = Vec::new(); + + for byte in prefix.bytes() { + result.push(byte & 0x1f); // CASHADDR-SPECIFIC: Only lower 5 bits (not Bech32's two-part expansion) + } + result.push(0); // Separator + + result +} + +/// Compute the cashaddr polymod checksum as per the spec. +/// +/// # CashAddr vs Bech32 Checksum Algorithm +/// +/// This is the **core difference** between CashAddr and Bech32. They use completely +/// different checksum algorithms that are incompatible with each other. +/// +/// ## Bech32/Bech32m Checksum +/// - Uses BCH (Bose-Chaudhuri-Hocquenghem) codes +/// - Generator polynomial: `x^6 + x + 1` (for Bech32) or modified constant (for Bech32m) +/// - Produces 6 checksum characters (30 bits) +/// - 40-bit checksum state +/// - Final XOR with constant: `1` (Bech32) or `0x2bc830a3` (Bech32m) +/// +/// ## CashAddr Checksum (This Function) +/// - Uses custom polymod with 5 generator polynomials +/// - Generators (40-bit values): +/// - `0x98f2bc8e61` +/// - `0x79b76d99e2` +/// - `0xf33e5fb3c4` +/// - `0xae2eabe2a8` +/// - `0x1e4f43e470` +/// - Produces 8 checksum characters (40 bits) +/// - 40-bit checksum state +/// - Initial and final XOR with `1` +/// +/// ## Algorithm Steps +/// 1. Initialize checksum state `c = 1` +/// 2. For each input value: +/// - Extract top 5 bits of state: `c0 = c >> 35` +/// - Shift state left 5 bits and XOR with input: `c = (c & 0x07ffffffff) << 5 ^ input` +/// - Apply generators based on bits in `c0` +/// 3. Final XOR with `1` +/// +/// Reference: https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md +fn polymod(values: &[u8]) -> u64 { + // CASHADDR-SPECIFIC: These generators are unique to CashAddr and incompatible with Bech32 + let generators: [u64; 5] = [ + 0x98f2bc8e61, + 0x79b76d99e2, + 0xf33e5fb3c4, + 0xae2eabe2a8, + 0x1e4f43e470, + ]; + + let mut c: u64 = 1; + for &d in values { + let c0 = (c >> 35) as u8; + c = ((c & 0x07ffffffff) << 5) ^ (d as u64); + + for i in 0..5 { + if (c0 & (1 << i)) != 0 { + c ^= generators[i]; + } + } + } + + c ^ 1 +} + +/// Encode hash to cashaddr format +pub fn encode_cashaddr(hash: &[u8], is_p2sh: bool, prefix: &str) -> Result { + if hash.len() != 20 { + return Err(AddressError::CashaddrError( + "Hash must be 20 bytes".to_string(), + )); + } + + // Version byte encodes type and size + // For 20 bytes: size_bits = 0 + // type_bit: 0 for P2PKH, 1 for P2SH + let version_byte = if is_p2sh { 0x08 } else { 0x00 }; + + let mut payload = vec![version_byte]; + payload.extend_from_slice(hash); + + // Convert to 5-bit values + let payload_5bit = bytes_to_fes(&payload)?; + + // Build the data to feed to polymod: prefix + payload + 8 zeros for checksum + let mut data = expand_prefix(prefix); + for fe in &payload_5bit { + data.push(fe.to_u8()); + } + // Add 8 zeros for the checksum placeholder + data.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0]); + + // Calculate checksum + let checksum = polymod(&data); + + // Extract checksum as 8 5-bit values + let mut checksum_fes = Vec::new(); + for i in 0..8 { + let fe_val = ((checksum >> (5 * (7 - i))) & 0x1f) as u8; + checksum_fes.push(Fe32::try_from(fe_val).expect("valid fe32")); + } + + // Combine payload and checksum + let mut combined = payload_5bit; + combined.extend(checksum_fes); + + // Encode to string + let mut result = format!("{}:", prefix); + for fe in combined { + result.push(fe.to_char()); + } + + Ok(result) +} + +/// Decode cashaddr to (hash, is_p2sh) +pub fn decode_cashaddr(address: &str, expected_prefix: &str) -> Result<(Vec, bool)> { + // Check for mixed case + let has_lower = address.chars().any(|c| c.is_lowercase()); + let has_upper = address.chars().any(|c| c.is_uppercase()); + if has_lower && has_upper { + return Err(AddressError::CashaddrError( + "Mixed case address".to_string(), + )); + } + + let address = address.to_lowercase(); + + // Split prefix + let (prefix, payload_str) = if let Some(colon_pos) = address.find(':') { + let (p, rest) = address.split_at(colon_pos); + (p, &rest[1..]) + } else { + (expected_prefix, address.as_str()) + }; + + if prefix != expected_prefix { + return Err(AddressError::CashaddrError(format!( + "Prefix mismatch: expected {}, got {}", + expected_prefix, prefix + ))); + } + + // Decode payload to field elements + let mut payload_fes = Vec::new(); + for ch in payload_str.chars() { + let fe = Fe32::from_char(ch) + .map_err(|_| AddressError::CashaddrError(format!("Invalid character: {}", ch)))?; + payload_fes.push(fe); + } + + // Verify checksum + let mut data = expand_prefix(prefix); + for fe in &payload_fes { + data.push(fe.to_u8()); + } + + let checksum = polymod(&data); + if checksum != 0 { + return Err(AddressError::CashaddrError("Invalid checksum".to_string())); + } + + // Remove checksum (last 8 elements) + let payload_fes = &payload_fes[..payload_fes.len() - 8]; + + // Convert back to 8-bit + let payload = fes_to_bytes(payload_fes)?; + + if payload.is_empty() { + return Err(AddressError::CashaddrError("Empty payload".to_string())); + } + + let version_byte = payload[0]; + let hash = payload[1..].to_vec(); + + if hash.len() != 20 { + return Err(AddressError::CashaddrError( + "Invalid hash length".to_string(), + )); + } + + let is_p2sh = (version_byte & 0x08) != 0; + + Ok((hash, is_p2sh)) +} + +impl AddressCodec for CashAddrCodec { + fn encode(&self, script: &Script) -> Result { + if script.is_p2pkh() { + if script.len() != 25 { + return Err(AddressError::InvalidScript( + "Invalid P2PKH script length".to_string(), + )); + } + let hash = &script.as_bytes()[3..23]; + encode_cashaddr(hash, false, self.prefix) + } else if script.is_p2sh() { + if script.len() != 23 { + return Err(AddressError::InvalidScript( + "Invalid P2SH script length".to_string(), + )); + } + let hash = &script.as_bytes()[2..22]; + encode_cashaddr(hash, true, self.prefix) + } else { + Err(AddressError::UnsupportedScriptType( + "CashAddr only supports P2PKH and P2SH".to_string(), + )) + } + } + + fn decode(&self, address: &str) -> Result { + let (hash, is_p2sh) = decode_cashaddr(address, self.prefix)?; + + let hash_array: [u8; 20] = hash + .try_into() + .map_err(|_| AddressError::CashaddrError("Invalid hash length".to_string()))?; + + if is_p2sh { + let script_hash = ScriptHash::from_byte_array(hash_array); + Ok(ScriptBuf::new_p2sh(&script_hash)) + } else { + let pubkey_hash = PubkeyHash::from_byte_array(hash_array); + Ok(ScriptBuf::new_p2pkh(&pubkey_hash)) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Test vectors from the official Bitcoin Cash CashAddr specification + /// https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md + /// + /// # Why These Tests Use CashAddr-Specific Implementation + /// + /// These tests validate that our implementation follows the CashAddr specification exactly, + /// which is incompatible with Bech32. Key differences validated by these tests: + /// + /// 1. **Checksum**: CashAddr's polymod with 5 generators produces different checksums than Bech32 + /// 2. **Prefix**: CashAddr uses `:` separator and simpler expansion (lower 5 bits only) + /// 3. **Padding**: Strict validation rejects non-zero padding bits + /// 4. **Format**: Version byte encodes type (P2PKH/P2SH) differently than Bech32's witness version + /// + /// Using bech32 crate's `ByteIterExt::bytes_to_fes()` or checksum functions would fail these tests + /// because they implement Bech32/Bech32m logic, not CashAddr logic. + + // Test vector: 20-byte P2PKH payload + const TEST_HASH_20: &str = "F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9"; + + #[test] + fn test_spec_vector_bitcoincash_p2pkh_20() { + let hash = hex::decode(TEST_HASH_20).unwrap(); + + let address = encode_cashaddr(&hash, false, "bitcoincash").unwrap(); + assert_eq!( + address, + "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2" + ); + + // Test roundtrip + let (decoded_hash, is_p2sh) = decode_cashaddr(&address, "bitcoincash").unwrap(); + assert_eq!(decoded_hash, hash); + assert_eq!(is_p2sh, false); + } + + #[test] + fn test_spec_vector_bchtest_p2sh_20() { + let hash = hex::decode(TEST_HASH_20).unwrap(); + + let address = encode_cashaddr(&hash, true, "bchtest").unwrap(); + assert_eq!( + address, + "bchtest:pr6m7j9njldwwzlg9v7v53unlr4jkmx6eyvwc0uz5t" + ); + + // Test roundtrip + let (decoded_hash, is_p2sh) = decode_cashaddr(&address, "bchtest").unwrap(); + assert_eq!(decoded_hash, hash); + assert_eq!(is_p2sh, true); + } + + #[test] + fn test_spec_vector_pref_p2sh_20() { + let hash = hex::decode(TEST_HASH_20).unwrap(); + + let address = encode_cashaddr(&hash, true, "pref").unwrap(); + assert_eq!(address, "pref:pr6m7j9njldwwzlg9v7v53unlr4jkmx6ey65nvtks5"); + + // Test roundtrip + let (decoded_hash, is_p2sh) = decode_cashaddr(&address, "pref").unwrap(); + assert_eq!(decoded_hash, hash); + assert_eq!(is_p2sh, true); + } + + #[test] + fn test_legacy_to_cashaddr_translation() { + // Test vectors from the spec showing legacy to cashaddr translation + let test_cases = vec![ + // (hash_hex, is_p2sh, expected_cashaddr) + // 1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu -> bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a + ( + "76a04053bda0a88bda5177b86a15c3b29f559873", + false, + "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a", + ), + // 1KXrWXciRDZUpQwQmuM1DbwsKDLYAYsVLR -> bitcoincash:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy + ( + "cb481232299cd5743151ac4b2d63ae198e7bb0a9", + false, + "bitcoincash:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy", + ), + // 16w1D5WRVKJuZUsSRzdLp9w3YGcgoxDXb -> bitcoincash:qqq3728yw0y47sqn6l2na30mcw6zm78dzqre909m2r + ( + "011f28e473c95f4013d7d53ec5fbc3b42df8ed10", + false, + "bitcoincash:qqq3728yw0y47sqn6l2na30mcw6zm78dzqre909m2r", + ), + // 3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC -> bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq + ( + "76a04053bda0a88bda5177b86a15c3b29f559873", + true, + "bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq", + ), + // 3LDsS579y7sruadqu11beEJoTjdFiFCdX4 -> bitcoincash:pr95sy3j9xwd2ap32xkykttr4cvcu7as4yc93ky28e + ( + "cb481232299cd5743151ac4b2d63ae198e7bb0a9", + true, + "bitcoincash:pr95sy3j9xwd2ap32xkykttr4cvcu7as4yc93ky28e", + ), + // 31nwvkZwyPdgzjBJZXfDmSWsC4ZLKpYyUw -> bitcoincash:pqq3728yw0y47sqn6l2na30mcw6zm78dzq5ucqzc37 + ( + "011f28e473c95f4013d7d53ec5fbc3b42df8ed10", + true, + "bitcoincash:pqq3728yw0y47sqn6l2na30mcw6zm78dzq5ucqzc37", + ), + ]; + + for (hash_hex, is_p2sh, expected) in test_cases { + let hash = hex::decode(hash_hex).unwrap(); + let address = encode_cashaddr(&hash, is_p2sh, "bitcoincash").unwrap(); + assert_eq!(address, expected, "Failed for hash {}", hash_hex); + + // Test roundtrip + let (decoded_hash, decoded_is_p2sh) = decode_cashaddr(&address, "bitcoincash").unwrap(); + assert_eq!(hex::encode(decoded_hash), hash_hex); + assert_eq!(decoded_is_p2sh, is_p2sh); + } + } + + #[test] + fn test_address_without_prefix() { + // The spec allows addresses without the prefix:colon part + let hash = hex::decode(TEST_HASH_20).unwrap(); + + let full_address = "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2"; + let no_prefix = "qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2"; + + let (decoded_full, is_p2sh_full) = decode_cashaddr(full_address, "bitcoincash").unwrap(); + let (decoded_no_prefix, is_p2sh_no_prefix) = + decode_cashaddr(no_prefix, "bitcoincash").unwrap(); + + assert_eq!(decoded_full, decoded_no_prefix); + assert_eq!(is_p2sh_full, is_p2sh_no_prefix); + assert_eq!(decoded_full, hash); + } + + #[test] + fn test_uppercase_address() { + // The spec states that uppercase is accepted but lowercase is preferred + let uppercase = "BITCOINCASH:QR6M7J9NJLDWWZLG9V7V53UNLR4JKMX6EYLEP8EKG2"; + let (hash, is_p2sh) = decode_cashaddr(uppercase, "bitcoincash").unwrap(); + + assert_eq!(hex::encode(hash).to_uppercase(), TEST_HASH_20); + assert_eq!(is_p2sh, false); + } + + #[test] + fn test_mixed_case_rejected() { + // The spec requires that mixed case must be rejected + let mixed_case = "bitcoincash:Qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2"; + assert!(decode_cashaddr(mixed_case, "bitcoincash").is_err()); + } + + #[test] + fn test_wrong_prefix() { + let address = "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2"; + // Try to decode with wrong expected prefix + assert!(decode_cashaddr(address, "ecash").is_err()); + } + + #[test] + fn test_invalid_checksum() { + // Modify the last character to break the checksum + let bad_address = "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg3"; + assert!(decode_cashaddr(bad_address, "bitcoincash").is_err()); + } + + #[test] + fn test_invalid_character() { + // 'b' is not in the base32 charset used by cashaddr + let bad_address = "bitcoincash:br6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2"; + assert!(decode_cashaddr(bad_address, "bitcoincash").is_err()); + } + + #[test] + fn test_ecash_vectors() { + // Test with eCash prefix + let hash = hex::decode(TEST_HASH_20).unwrap(); + + let address = encode_cashaddr(&hash, false, "ecash").unwrap(); + // Note: eCash uses same encoding, just different prefix + assert!(address.starts_with("ecash:")); + + // Test roundtrip + let (decoded_hash, is_p2sh) = decode_cashaddr(&address, "ecash").unwrap(); + assert_eq!(decoded_hash, hash); + assert_eq!(is_p2sh, false); + } + + #[test] + fn test_codec_trait_implementation() { + // Test using the AddressCodec trait - roundtrip test only + let hash = hex::decode(TEST_HASH_20).unwrap(); + let pubkey_hash = PubkeyHash::from_byte_array(hash.try_into().unwrap()); + let script = ScriptBuf::new_p2pkh(&pubkey_hash); + + let codec = CashAddrCodec::new("bitcoincash", 0x00, 0x08); + let address = codec.encode(&script).unwrap(); + + // Verify it's a bitcoincash address + assert!(address.starts_with("bitcoincash:")); + + // Test roundtrip: encode then decode should give us back the original script + let decoded_script = codec.decode(&address).unwrap(); + assert_eq!(decoded_script, script); + } + + #[test] + fn test_p2sh_script_type() { + let hash = hex::decode(TEST_HASH_20).unwrap(); + let script_hash = ScriptHash::from_byte_array(hash.try_into().unwrap()); + let script = ScriptBuf::new_p2sh(&script_hash); + + let codec = CashAddrCodec::new("bitcoincash", 0x00, 0x08); + let address = codec.encode(&script).unwrap(); + + // P2SH addresses start with 'p' after the prefix + assert!(address.contains(":p")); + + // Test roundtrip + let decoded_script = codec.decode(&address).unwrap(); + assert_eq!(decoded_script, script); + } +} diff --git a/packages/wasm-utxo/src/address/mod.rs b/packages/wasm-utxo/src/address/mod.rs new file mode 100644 index 0000000..824ea1e --- /dev/null +++ b/packages/wasm-utxo/src/address/mod.rs @@ -0,0 +1,591 @@ +//! Bitcoin address encoding and decoding for multiple networks and formats. +//! +//! This module provides address format support for various cryptocurrency networks including: +//! - Bitcoin and Bitcoin-like coins (BTC, LTC, BCH, BSV, BTG, DASH, DOGE) +//! - eCash +//! - Zcash +//! +//! # Supported Address Formats +//! +//! - **Base58Check**: Traditional P2PKH and P2SH addresses +//! - **Bech32/Bech32m**: Native SegWit addresses (P2WPKH, P2WSH, P2TR) +//! - **Cashaddr**: Bitcoin Cash and eCash-specific format +//! +//! # Implementation Status +//! +//! ✅ **Working**: +//! - Base58Check encoding/decoding for all networks +//! - Bech32/Bech32m for witness programs (P2WPKH, P2WSH, P2TR) +//! - Cashaddr encoding/decoding for Bitcoin Cash and eCash (fully compliant with spec) +//! - Zcash multi-byte version support +//! - P2PKH, P2SH, P2WPKH, P2WSH, P2TR script types +//! +//! # Examples +//! +//! ```rust,ignore +//! use wasm_utxo::{BITCOIN, from_output_script, to_output_script}; +//! +//! // Decode a Bitcoin address +//! let script = to_output_script("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", &BITCOIN)?; +//! +//! // Encode a script to address +//! let address = from_output_script(&script, &BITCOIN)?; +//! ``` + +mod base58check; +mod bech32; +pub mod cashaddr; +pub mod utxolib_compat; + +pub use base58check::Base58CheckCodec; +pub use bech32::Bech32Codec; +pub use cashaddr::CashAddrCodec; + +use crate::bitcoin::{Script, ScriptBuf}; +use std::fmt; + +#[derive(Debug)] +pub enum AddressError { + InvalidScript(String), + InvalidAddress(String), + UnsupportedScriptType(String), + Base58Error(String), + Bech32Error(String), + CashaddrError(String), +} + +impl fmt::Display for AddressError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AddressError::InvalidScript(msg) => write!(f, "Invalid script: {}", msg), + AddressError::InvalidAddress(msg) => write!(f, "Invalid address: {}", msg), + AddressError::UnsupportedScriptType(msg) => { + write!(f, "Unsupported script type: {}", msg) + } + AddressError::Base58Error(msg) => write!(f, "Base58 error: {}", msg), + AddressError::Bech32Error(msg) => write!(f, "Bech32 error: {}", msg), + AddressError::CashaddrError(msg) => write!(f, "Cashaddr error: {}", msg), + } + } +} + +impl std::error::Error for AddressError {} + +type Result = std::result::Result; + +/// Trait for address encoding and decoding +pub trait AddressCodec { + fn encode(&self, script: &Script) -> Result; + fn decode(&self, address: &str) -> Result; +} + +// Network-specific codec parameters (values from src/chainparams.cpp in various coin implementations): +// +// Base58CheckCodec::new(pubkey_hash_version, script_hash_version) +// - pubkey_hash_version: base58Prefixes[PUBKEY_ADDRESS] for P2PKH addresses +// - script_hash_version: base58Prefixes[SCRIPT_ADDRESS] for P2SH addresses +// - Note: Zcash uses 2-byte versions (0x1cb8, 0x1cbd, etc.) +// +// Bech32Codec::new(hrp) +// - hrp: Human-readable part for bech32/bech32m addresses (e.g., "bc", "tb", "ltc") +// +// CashAddrCodec::new(prefix, pubkey_type, script_type) +// - prefix: Network prefix (e.g., "bitcoincash", "ecash") +// - pubkey_type: Type byte for P2PKH (typically 0x00) +// - script_type: Type byte for P2SH (typically 0x08) + +// Bitcoin variants (Base58Check + Bech32) +// https://github.com/bitcoin/bitcoin/blob/master/src/validation.cpp +// https://github.com/bitcoin/bitcoin/blob/master/src/chainparams.cpp +pub const BITCOIN: Base58CheckCodec = Base58CheckCodec::new(0x00, 0x05); +pub const BITCOIN_BECH32: Bech32Codec = Bech32Codec::new("bc"); + +pub const TESTNET: Base58CheckCodec = Base58CheckCodec::new(0x6f, 0xc4); +pub const TESTNET_BECH32: Bech32Codec = Bech32Codec::new("tb"); + +// Bitcoin Cash (Base58Check) +// https://github.com/bitcoin-cash-node/bitcoin-cash-node/blob/master/src/validation.cpp +// https://github.com/bitcoin-cash-node/bitcoin-cash-node/blob/master/src/chainparams.cpp +pub const BITCOIN_CASH: Base58CheckCodec = Base58CheckCodec::new(0x00, 0x05); +pub const BITCOIN_CASH_TESTNET: Base58CheckCodec = Base58CheckCodec::new(0x6f, 0xc4); + +// Bitcoin Cash (Cashaddr) +// https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md +pub const BITCOIN_CASH_CASHADDR: CashAddrCodec = CashAddrCodec::new("bitcoincash", 0x00, 0x08); +pub const BITCOIN_CASH_TESTNET_CASHADDR: CashAddrCodec = CashAddrCodec::new("bchtest", 0x00, 0x08); + +// eCash (Base58Check) +// https://github.com/Bitcoin-ABC/bitcoin-abc/blob/master/src/validation.cpp +// https://github.com/Bitcoin-ABC/bitcoin-abc/blob/master/src/chainparams.cpp +pub const ECASH: Base58CheckCodec = Base58CheckCodec::new(0x00, 0x05); +pub const ECASH_TEST: Base58CheckCodec = Base58CheckCodec::new(0x6f, 0xc4); + +// eCash (Cashaddr) +pub const ECASH_CASHADDR: CashAddrCodec = CashAddrCodec::new("ecash", 0x00, 0x08); +pub const ECASH_TEST_CASHADDR: CashAddrCodec = CashAddrCodec::new("ectest", 0x00, 0x08); + +// Bitcoin Gold +// https://github.com/BTCGPU/BTCGPU/blob/master/src/validation.cpp +// https://github.com/BTCGPU/BTCGPU/blob/master/src/chainparams.cpp +pub const BITCOIN_GOLD: Base58CheckCodec = Base58CheckCodec::new(0x26, 0x17); +pub const BITCOIN_GOLD_BECH32: Bech32Codec = Bech32Codec::new("btg"); + +pub const BITCOIN_GOLD_TESTNET: Base58CheckCodec = Base58CheckCodec::new(0x6f, 0xc4); +pub const BITCOIN_GOLD_TESTNET_BECH32: Bech32Codec = Bech32Codec::new("tbtg"); + +// Bitcoin SV +// https://github.com/bitcoin-sv/bitcoin-sv/blob/master/src/validation.cpp +// https://github.com/bitcoin-sv/bitcoin-sv/blob/master/src/chainparams.cpp +pub const BITCOIN_SV: Base58CheckCodec = Base58CheckCodec::new(0x00, 0x05); +pub const BITCOIN_SV_TESTNET: Base58CheckCodec = Base58CheckCodec::new(0x6f, 0xc4); + +// Litecoin +// https://github.com/litecoin-project/litecoin/blob/master/src/validation.cpp +// https://github.com/litecoin-project/litecoin/blob/master/src/chainparams.cpp +pub const LITECOIN: Base58CheckCodec = Base58CheckCodec::new(0x30, 0x32); +pub const LITECOIN_BECH32: Bech32Codec = Bech32Codec::new("ltc"); + +pub const LITECOIN_TEST: Base58CheckCodec = Base58CheckCodec::new(0x6f, 0x3a); +pub const LITECOIN_TEST_BECH32: Bech32Codec = Bech32Codec::new("tltc"); + +// Dogecoin +// https://github.com/dogecoin/dogecoin/blob/master/src/validation.cpp +// https://github.com/dogecoin/dogecoin/blob/master/src/chainparams.cpp +// Mainnet bip32 does not match dogecoin core (see BG-53241) +pub const DOGECOIN: Base58CheckCodec = Base58CheckCodec::new(0x1e, 0x16); +pub const DOGECOIN_TEST: Base58CheckCodec = Base58CheckCodec::new(0x71, 0xc4); + +// Dash +// https://github.com/dashpay/dash/blob/master/src/validation.cpp +// https://github.com/dashpay/dash/blob/master/src/chainparams.cpp +pub const DASH: Base58CheckCodec = Base58CheckCodec::new(0x4c, 0x10); +pub const DASH_TEST: Base58CheckCodec = Base58CheckCodec::new(0x8c, 0x13); + +// Zcash (uses 2-byte version prefixes) +// https://github.com/zcash/zcash/blob/master/src/validation.cpp +// https://github.com/zcash/zcash/blob/master/src/chainparams.cpp +pub const ZCASH: Base58CheckCodec = Base58CheckCodec::new(0x1cb8, 0x1cbd); +pub const ZCASH_TEST: Base58CheckCodec = Base58CheckCodec::new(0x1d25, 0x1cba); + +/// Convert output script to address string (convenience wrapper) +pub fn from_output_script(script: &Script, codec: &dyn AddressCodec) -> Result { + codec.encode(script) +} + +/// Convert address string to output script (convenience wrapper) +pub fn to_output_script(address: &str, codec: &dyn AddressCodec) -> Result { + codec.decode(address) +} + +/// Try multiple codecs to decode an address +pub fn to_output_script_try_codecs( + address: &str, + codecs: &[&dyn AddressCodec], +) -> Result { + for &codec in codecs { + if let Ok(script) = codec.decode(address) { + return Ok(script); + } + } + + Err(AddressError::InvalidAddress(format!( + "Could not decode address with any provided codec: {}", + address + ))) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::bitcoin::hashes::Hash; + use crate::bitcoin::PubkeyHash; + + #[test] + fn test_base58_roundtrip() { + let hash = hex::decode("1e231c7f9b3415daaa53ee5a7e12e120f00ec212").unwrap(); + let pubkey_hash = PubkeyHash::from_byte_array(hash.try_into().unwrap()); + let script = ScriptBuf::new_p2pkh(&pubkey_hash); + + let encoded = from_output_script(&script, &BITCOIN).unwrap(); + let decoded_script = to_output_script(&encoded, &BITCOIN).unwrap(); + + assert_eq!(script, decoded_script); + } + + #[test] + fn test_zcash_base58() { + // Zcash uses 2-byte version prefixes + let hash = [0; 20]; + let pubkey_hash = PubkeyHash::from_byte_array(hash); + let script = ScriptBuf::new_p2pkh(&pubkey_hash); + + let encoded = from_output_script(&script, &ZCASH).unwrap(); + let decoded_script = to_output_script(&encoded, &ZCASH).unwrap(); + + assert_eq!(script, decoded_script); + } + + #[test] + fn test_cashaddr_roundtrip() { + // Test that our cashaddr implementation is internally consistent (encode/decode roundtrip) + let hash = hex::decode("F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9").unwrap(); + let pubkey_hash = PubkeyHash::from_byte_array(hash.try_into().unwrap()); + let script = ScriptBuf::new_p2pkh(&pubkey_hash); + + let encoded = from_output_script(&script, &BITCOIN_CASH_CASHADDR).unwrap(); + eprintln!("Encoded cashaddr: {}", encoded); + + // Ensure we can decode what we encoded + let decoded_script = to_output_script(&encoded, &BITCOIN_CASH_CASHADDR).unwrap(); + assert_eq!(script, decoded_script); + } + + #[test] + fn test_cashaddr_encode_decode() { + let hash = hex::decode("F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9").unwrap(); + let pubkey_hash = PubkeyHash::from_byte_array(hash.try_into().unwrap()); + let script = ScriptBuf::new_p2pkh(&pubkey_hash); + + let encoded = from_output_script(&script, &BITCOIN_CASH_CASHADDR).unwrap(); + // Correct checksum according to the official spec + assert_eq!( + encoded, + "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2" + ); + + let decoded_script = to_output_script(&encoded, &BITCOIN_CASH_CASHADDR).unwrap(); + assert_eq!(script, decoded_script); + } + + #[test] + fn test_cashaddr_no_prefix() { + let hash = hex::decode("F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9").unwrap(); + let pubkey_hash = PubkeyHash::from_byte_array(hash.try_into().unwrap()); + let script = ScriptBuf::new_p2pkh(&pubkey_hash); + let encoded = "qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2"; + + let decoded_script = to_output_script(encoded, &BITCOIN_CASH_CASHADDR).unwrap(); + assert_eq!(script, decoded_script); + } + + #[test] + fn test_cashaddr_uppercase() { + let encoded = "BITCOINCASH:QR6M7J9NJLDWWZLG9V7V53UNLR4JKMX6EYLEP8EKG2"; + let _ = to_output_script(encoded, &BITCOIN_CASH_CASHADDR).unwrap(); + } + + #[test] + fn test_cashaddr_mixed_case_rejected() { + let encoded = "bitcoincash:Qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2"; + assert!(to_output_script(encoded, &BITCOIN_CASH_CASHADDR).is_err()); + } + + // Helper to encode Bitcoin addresses (tries both base58 and bech32) + fn bitcoin_encode(script: &[u8]) -> Result { + let script_obj = Script::from_bytes(script); + if script_obj.is_p2pkh() || script_obj.is_p2sh() { + from_output_script(script_obj, &BITCOIN) + } else if script_obj.is_p2wpkh() || script_obj.is_p2wsh() || script_obj.is_p2tr() { + from_output_script(script_obj, &BITCOIN_BECH32) + } else { + Err(AddressError::UnsupportedScriptType(format!( + "Unknown script type, length: {}", + script.len() + ))) + } + } + + // Helper to decode Bitcoin addresses (tries both base58 and bech32) + fn bitcoin_decode(address: &str) -> Result { + to_output_script_try_codecs(address, &[&BITCOIN, &BITCOIN_BECH32]) + } + + // Helper for testnet + fn testnet_encode(script: &[u8]) -> Result { + let script_obj = Script::from_bytes(script); + if script_obj.is_p2pkh() || script_obj.is_p2sh() { + from_output_script(script_obj, &TESTNET) + } else if script_obj.is_p2wpkh() || script_obj.is_p2wsh() || script_obj.is_p2tr() { + from_output_script(script_obj, &TESTNET_BECH32) + } else { + Err(AddressError::UnsupportedScriptType(format!( + "Unknown script type, length: {}", + script.len() + ))) + } + } + + // Helper for Litecoin + fn litecoin_encode(script: &[u8]) -> Result { + let script_obj = Script::from_bytes(script); + if script_obj.is_p2pkh() || script_obj.is_p2sh() { + from_output_script(script_obj, &LITECOIN) + } else if script_obj.is_p2wpkh() || script_obj.is_p2wsh() || script_obj.is_p2tr() { + from_output_script(script_obj, &LITECOIN_BECH32) + } else { + Err(AddressError::UnsupportedScriptType(format!( + "Unknown script type, length: {}", + script.len() + ))) + } + } + + fn load_fixture(network: &str) -> Vec<(String, String, String)> { + let fixture_path = format!("test/fixtures/address/{}.json", network); + let content = std::fs::read_to_string(&fixture_path) + .unwrap_or_else(|_| panic!("Failed to load fixture: {}", fixture_path)); + + let parsed: Vec = serde_json::from_str(&content) + .unwrap_or_else(|_| panic!("Failed to parse fixture: {}", fixture_path)); + + parsed + .iter() + .map(|item| { + let arr = item.as_array().unwrap(); + ( + arr[0].as_str().unwrap().to_string(), + arr[1].as_str().unwrap().to_string(), + arr[2].as_str().unwrap().to_string(), + ) + }) + .collect() + } + + #[test] + fn test_bitcoin_addresses() { + let vectors = load_fixture("bitcoin"); + + for (script_type, script_hex, expected_address) in vectors { + let script = hex::decode(&script_hex).unwrap(); + let address = bitcoin_encode(&script).unwrap(); + assert_eq!( + address, expected_address, + "Failed for script type: {}", + script_type + ); + + // Round trip + let decoded_script = bitcoin_decode(&address).unwrap(); + assert_eq!( + hex::encode(decoded_script.as_bytes()), + script_hex, + "Round trip failed for: {}", + script_type + ); + } + } + + #[test] + fn test_testnet_addresses() { + let vectors = load_fixture("testnet"); + + for (script_type, script_hex, expected_address) in vectors { + let script = hex::decode(&script_hex).unwrap(); + let address = testnet_encode(&script).unwrap(); + assert_eq!( + address, expected_address, + "Failed for script type: {}", + script_type + ); + } + } + + #[test] + fn test_bitcoincash_cashaddr() { + let vectors = load_fixture("bitcoincash-cashaddr"); + + for (_script_type, script_hex, expected_address) in vectors { + let script = hex::decode(&script_hex).unwrap(); + let script_obj = Script::from_bytes(&script); + let address = from_output_script(script_obj, &BITCOIN_CASH_CASHADDR).unwrap(); + assert_eq!(address, expected_address); + + // Round trip + let decoded_script = to_output_script(&address, &BITCOIN_CASH_CASHADDR).unwrap(); + assert_eq!(hex::encode(decoded_script.as_bytes()), script_hex); + } + } + + #[test] + fn test_ecash_cashaddr() { + let vectors = load_fixture("ecash-cashaddr"); + + for (_script_type, script_hex, expected_address) in vectors { + let script = hex::decode(&script_hex).unwrap(); + let script_obj = Script::from_bytes(&script); + let address = from_output_script(script_obj, &ECASH_CASHADDR).unwrap(); + assert_eq!(address, expected_address); + } + } + + #[test] + fn test_litecoin_addresses() { + let vectors = load_fixture("litecoin"); + + for (_script_type, script_hex, expected_address) in vectors { + let script = hex::decode(&script_hex).unwrap(); + let address = litecoin_encode(&script).unwrap(); + assert_eq!(address, expected_address); + } + } + + #[test] + fn test_try_codecs() { + let codecs: &[&dyn AddressCodec] = &[&BITCOIN_CASH, &BITCOIN_CASH_CASHADDR]; + + // Test with cashaddr address + let address = "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2"; + let _script = to_output_script_try_codecs(address, codecs).unwrap(); + // Successfully decoded with one of the codecs + } + + /// Maps fixture filename to appropriate codec(s) for encoding + fn get_codecs_for_fixture(filename: &str) -> Vec<&'static dyn AddressCodec> { + match filename { + "bitcoin.json" => vec![&BITCOIN as &dyn AddressCodec, &BITCOIN_BECH32], + "testnet.json" => vec![&TESTNET, &TESTNET_BECH32], + "bitcoinPublicSignet.json" => vec![&TESTNET, &TESTNET_BECH32], + "bitcoincash.json" => vec![&BITCOIN_CASH], + "bitcoincash-cashaddr.json" => vec![&BITCOIN_CASH_CASHADDR], + "bitcoincashTestnet.json" => vec![&BITCOIN_CASH_TESTNET], + "bitcoincashTestnet-cashaddr.json" => vec![&BITCOIN_CASH_TESTNET_CASHADDR], + "bitcoingold.json" => vec![&BITCOIN_GOLD, &BITCOIN_GOLD_BECH32], + "bitcoingoldTestnet.json" => vec![&BITCOIN_GOLD_TESTNET, &BITCOIN_GOLD_TESTNET_BECH32], + "bitcoinsv.json" => vec![&BITCOIN_SV], + "bitcoinsvTestnet.json" => vec![&BITCOIN_SV_TESTNET], + "dash.json" => vec![&DASH], + "dashTest.json" => vec![&DASH_TEST], + "dogecoin.json" => vec![&DOGECOIN], + "dogecoinTest.json" => vec![&DOGECOIN_TEST], + "ecash.json" => vec![&ECASH], + "ecash-cashaddr.json" => vec![&ECASH_CASHADDR], + "ecashTest.json" => vec![&ECASH_TEST], + "ecashTest-cashaddr.json" => vec![&ECASH_TEST_CASHADDR], + "ecashTestnet.json" => vec![&ECASH_TEST], + "ecashTestnet-cashaddr.json" => vec![&ECASH_TEST_CASHADDR], + "litecoin.json" => vec![&LITECOIN, &LITECOIN_BECH32], + "litecoinTest.json" => vec![&LITECOIN_TEST, &LITECOIN_TEST_BECH32], + "zcash.json" => vec![&ZCASH], + "zcashTest.json" => vec![&ZCASH_TEST], + _ => panic!("Unknown fixture file: {}", filename), + } + } + + /// Helper to encode an address using the appropriate codec based on script type + fn encode_with_codecs(script: &[u8], codecs: &[&dyn AddressCodec]) -> Result { + let script_obj = Script::from_bytes(script); + + // For networks with both base58 and bech32, choose based on script type + let codec = if script_obj.is_p2pkh() || script_obj.is_p2sh() { + codecs[0] + } else if script_obj.is_p2wpkh() || script_obj.is_p2wsh() || script_obj.is_p2tr() { + // Use bech32 codec if available (index 1), otherwise fall back to base58 + if codecs.len() > 1 { + codecs[1] + } else { + codecs[0] + } + } else { + return Err(AddressError::UnsupportedScriptType(format!( + "Unknown script type, length: {}", + script.len() + ))); + }; + + codec.encode(script_obj) + } + + #[test] + fn test_all_fixtures() { + let fixtures_dir = "test/fixtures/address"; + + // Read all JSON files in the fixtures directory + let entries = std::fs::read_dir(fixtures_dir) + .unwrap_or_else(|_| panic!("Failed to read fixtures directory: {}", fixtures_dir)); + + let mut fixture_files: Vec<_> = entries + .filter_map(|entry| { + let entry = entry.ok()?; + let path = entry.path(); + if path.extension()? == "json" { + Some(path) + } else { + None + } + }) + .collect(); + + // Sort for deterministic test order + fixture_files.sort(); + + assert!( + !fixture_files.is_empty(), + "No fixture files found in {}", + fixtures_dir + ); + + for fixture_path in fixture_files { + let filename = fixture_path.file_name().unwrap().to_str().unwrap(); + eprintln!("\nTesting fixture: {}", filename); + + // Get the appropriate codecs for this fixture + let codecs = get_codecs_for_fixture(filename); + + // Load and parse the fixture + let content = std::fs::read_to_string(&fixture_path) + .unwrap_or_else(|_| panic!("Failed to read fixture: {:?}", fixture_path)); + + let parsed: Vec = serde_json::from_str(&content) + .unwrap_or_else(|_| panic!("Failed to parse fixture: {:?}", fixture_path)); + + // Test each vector in the fixture + for (idx, item) in parsed.iter().enumerate() { + let arr = item.as_array().unwrap(); + let script_type = arr[0].as_str().unwrap(); + let script_hex = arr[1].as_str().unwrap(); + let expected_address = arr[2].as_str().unwrap(); + + let script = hex::decode(script_hex).unwrap_or_else(|_| { + panic!( + "Failed to decode script hex in {}[{}]: {}", + filename, idx, script_hex + ) + }); + + // Test encoding + let encoded_address = encode_with_codecs(&script, &codecs).unwrap_or_else(|e| { + panic!( + "Failed to encode {}[{}] ({}): {:?}", + filename, idx, script_type, e + ) + }); + + assert_eq!( + encoded_address, expected_address, + "Encoding mismatch in {}[{}] ({})", + filename, idx, script_type + ); + + // Test decoding (round trip) + let decoded_script = to_output_script_try_codecs(&encoded_address, &codecs) + .unwrap_or_else(|e| { + panic!( + "Failed to decode {}[{}] ({}): {:?}", + filename, idx, script_type, e + ) + }); + + assert_eq!( + hex::encode(decoded_script.as_bytes()), + script_hex, + "Decoding mismatch in {}[{}] ({})", + filename, + idx, + script_type + ); + } + + eprintln!("✓ {} passed ({} vectors)", filename, parsed.len()); + } + } +} diff --git a/packages/wasm-utxo/src/address/utxolib_compat.rs b/packages/wasm-utxo/src/address/utxolib_compat.rs new file mode 100644 index 0000000..eb5e64f --- /dev/null +++ b/packages/wasm-utxo/src/address/utxolib_compat.rs @@ -0,0 +1,148 @@ +/// Helper structs for compatibility with npm @bitgo/utxo-lib +/// Long-term we should not use the `Network` objects from @bitgo/utxo-lib any longer, +/// but for now we need to keep this compatibility layer. +use wasm_bindgen::JsValue; + +use crate::address::{bech32, cashaddr, Base58CheckCodec}; +use crate::bitcoin::{Script, ScriptBuf}; + +pub use crate::address::AddressError; + +type Result = std::result::Result; + +pub struct CashAddr { + pub prefix: String, + pub pub_key_hash: u32, + pub script_hash: u32, +} + +pub struct Network { + pub pub_key_hash: u32, + pub script_hash: u32, + pub cash_addr: Option, + pub bech32: Option, +} + +impl Network { + /// Parse a Network object from a JavaScript value + pub fn from_js_value(js_network: &JsValue) -> Result { + use crate::try_from_js_value::TryFromJsValue; + Network::try_from_js_value(js_network) + .map_err(|e| AddressError::InvalidAddress(e.to_string())) + } +} + +/// Convert output script to address string using a utxolib Network object +pub fn from_output_script_with_network(script: &Script, network: &Network) -> Result { + // Determine script type and choose appropriate codec + // Note: We always use base58check for P2PKH/P2SH to match utxolib behavior, + // even if cashAddr is available. Cashaddr is only used for decoding. + if script.is_p2pkh() || script.is_p2sh() { + let codec = Base58CheckCodec::new(network.pub_key_hash, network.script_hash); + use crate::address::AddressCodec; + codec.encode(script) + } else if script.is_p2wpkh() || script.is_p2wsh() || script.is_p2tr() { + // For witness scripts, use bech32 if available + if let Some(ref hrp) = network.bech32 { + let (witness_version, program) = bech32::extract_witness_program(script)?; + bech32::encode_witness_with_custom_hrp(program, witness_version, hrp) + } else { + Err(AddressError::UnsupportedScriptType( + "Network does not support bech32 addresses".to_string(), + )) + } + } else { + Err(AddressError::UnsupportedScriptType(format!( + "Unsupported script type for address encoding, length: {}", + script.len() + ))) + } +} + +/// Convert address string to output script using a utxolib Network object +pub fn to_output_script_with_network(address: &str, network: &Network) -> Result { + use crate::address::AddressCodec; + use crate::bitcoin::hashes::Hash; + use crate::bitcoin::{PubkeyHash, ScriptHash}; + + // Try base58check first (always available) + let base58_codec = Base58CheckCodec::new(network.pub_key_hash, network.script_hash); + if let Ok(script) = base58_codec.decode(address) { + return Ok(script); + } + + // Try bech32 if available + if let Some(ref hrp) = network.bech32 { + if let Ok(script_bytes) = bech32::decode_witness_with_custom_hrp(address, hrp) { + return Ok(ScriptBuf::from_bytes(script_bytes)); + } + } + + // Try cashaddr if available + if let Some(ref cash_addr) = network.cash_addr { + if let Ok((hash, is_p2sh)) = cashaddr::decode_cashaddr(address, &cash_addr.prefix) { + let hash_array: [u8; 20] = hash + .try_into() + .map_err(|_| AddressError::CashaddrError("Invalid hash length".to_string()))?; + + return if is_p2sh { + let script_hash = ScriptHash::from_byte_array(hash_array); + Ok(ScriptBuf::new_p2sh(&script_hash)) + } else { + let pubkey_hash = PubkeyHash::from_byte_array(hash_array); + Ok(ScriptBuf::new_p2pkh(&pubkey_hash)) + }; + } + } + + Err(AddressError::InvalidAddress(format!( + "Could not decode address with any available codec: {}", + address + ))) +} + +// WASM bindings for utxolib-compatible address functions +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub struct Address; + +#[wasm_bindgen] +impl Address { + /// Convert output script to address string + /// + /// # Arguments + /// * `script` - The output script as a byte array + /// * `network` - The utxolib Network object from JavaScript + #[wasm_bindgen(js_name = fromOutputScript)] + pub fn from_output_script_js( + script: &[u8], + network: JsValue, + ) -> std::result::Result { + let network = + Network::from_js_value(&network).map_err(|e| JsValue::from_str(&e.to_string()))?; + + let script_obj = Script::from_bytes(script); + + from_output_script_with_network(script_obj, &network) + .map_err(|e| JsValue::from_str(&e.to_string())) + } + + /// Convert address string to output script + /// + /// # Arguments + /// * `address` - The address string + /// * `network` - The utxolib Network object from JavaScript + #[wasm_bindgen(js_name = toOutputScript)] + pub fn to_output_script_js( + address: &str, + network: JsValue, + ) -> std::result::Result, JsValue> { + let network = + Network::from_js_value(&network).map_err(|e| JsValue::from_str(&e.to_string()))?; + + to_output_script_with_network(address, &network) + .map(|script| script.to_bytes()) + .map_err(|e| JsValue::from_str(&e.to_string())) + } +} diff --git a/packages/wasm-utxo/src/error.rs b/packages/wasm-utxo/src/error.rs index 402fe20..3315a8c 100644 --- a/packages/wasm-utxo/src/error.rs +++ b/packages/wasm-utxo/src/error.rs @@ -43,3 +43,9 @@ impl WasmMiniscriptError { WasmMiniscriptError::StringError(s.to_string()) } } + +impl From for WasmMiniscriptError { + fn from(err: crate::address::AddressError) -> Self { + WasmMiniscriptError::StringError(err.to_string()) + } +} diff --git a/packages/wasm-utxo/src/lib.rs b/packages/wasm-utxo/src/lib.rs index 8f06067..9b10354 100644 --- a/packages/wasm-utxo/src/lib.rs +++ b/packages/wasm-utxo/src/lib.rs @@ -1,14 +1,17 @@ +mod address; mod descriptor; mod error; mod fixed_script_wallet; mod miniscript; mod psbt; +mod try_from_js_value; mod try_into_js_value; // re-export bitcoin from the miniscript crate // this package is transitioning to a all-purpose bitcoin package, so we want easy access pub use ::miniscript::bitcoin; +pub use address::utxolib_compat; pub use descriptor::WrapDescriptor; pub use miniscript::WrapMiniscript; pub use psbt::WrapPsbt; diff --git a/packages/wasm-utxo/src/try_from_js_value.rs b/packages/wasm-utxo/src/try_from_js_value.rs new file mode 100644 index 0000000..0461ce6 --- /dev/null +++ b/packages/wasm-utxo/src/try_from_js_value.rs @@ -0,0 +1,77 @@ +use crate::address::utxolib_compat::{CashAddr, Network}; +use crate::error::WasmMiniscriptError; +use wasm_bindgen::JsValue; + +pub(crate) trait TryFromJsValue { + fn try_from_js_value(value: &JsValue) -> Result + where + Self: Sized; +} + +// Implement TryFromJsValue for primitive types + +impl TryFromJsValue for String { + fn try_from_js_value(value: &JsValue) -> Result { + value + .as_string() + .ok_or_else(|| WasmMiniscriptError::new("Expected a string")) + } +} + +impl TryFromJsValue for u32 { + fn try_from_js_value(value: &JsValue) -> Result { + value + .as_f64() + .ok_or_else(|| WasmMiniscriptError::new("Expected a number")) + .map(|n| n as u32) + } +} + +impl TryFromJsValue for Option { + fn try_from_js_value(value: &JsValue) -> Result { + if value.is_undefined() || value.is_null() { + Ok(None) + } else { + T::try_from_js_value(value).map(Some) + } + } +} + +// Helper function to get a field from an object and convert it using TryFromJsValue +fn get_field(obj: &JsValue, key: &str) -> Result { + let field_value = js_sys::Reflect::get(obj, &JsValue::from_str(key)) + .map_err(|_| WasmMiniscriptError::new(&format!("Failed to read {} from object", key)))?; + + T::try_from_js_value(&field_value) + .map_err(|e| WasmMiniscriptError::new(&format!("{} (field: {})", e, key))) +} + +impl TryFromJsValue for Network { + fn try_from_js_value(value: &JsValue) -> Result { + let pub_key_hash = get_field(value, "pubKeyHash")?; + let script_hash = get_field(value, "scriptHash")?; + let bech32 = get_field(value, "bech32")?; + let cash_addr = get_field(value, "cashAddr")?; + + Ok(Network { + pub_key_hash, + script_hash, + cash_addr, + bech32, + }) + } +} + +impl TryFromJsValue for CashAddr { + fn try_from_js_value(value: &JsValue) -> Result { + let prefix = get_field(value, "prefix")?; + let pub_key_hash = get_field(value, "pubKeyHash")?; + let script_hash = get_field(value, "scriptHash")?; + + Ok(CashAddr { + prefix, + pub_key_hash, + script_hash, + }) + } +} diff --git a/packages/wasm-utxo/test/address/utxolibCompat.ts b/packages/wasm-utxo/test/address/utxolibCompat.ts new file mode 100644 index 0000000..d51676b --- /dev/null +++ b/packages/wasm-utxo/test/address/utxolibCompat.ts @@ -0,0 +1,42 @@ +import * as path from "node:path"; +import * as fs from "node:fs/promises"; + +import * as utxolib from "@bitgo/utxo-lib"; +import assert from "node:assert"; +import { utxolibCompat } from "../../js"; + +type Fixture = [type: string, script: string, address: string]; + +async function getFixtures(name: string): Promise { + if (name === "bitcoinBitGoSignet") { + name = "bitcoinPublicSignet"; + } + const fixturePath = path.join(__dirname, "..", "fixtures", "address", `${name}.json`); + const fixtures = await fs.readFile(fixturePath, "utf8"); + return JSON.parse(fixtures); +} + +function runTest(network: utxolib.Network) { + const name = utxolib.getNetworkName(network); + describe(`utxolibCompat ${name}`, function () { + let fixtures; + before(async function () { + fixtures = await getFixtures(name); + }); + + it("should convert to utxolib compatible network", async function () { + for (const fixture of fixtures) { + const [_type, script, addressRef] = fixture; + const scriptBuf = Buffer.from(script, "hex"); + const address = utxolibCompat.Address.fromOutputScript(scriptBuf, network); + assert.strictEqual(address, addressRef); + const scriptFromAddress = utxolibCompat.Address.toOutputScript(address, network); + assert.deepStrictEqual(Buffer.from(scriptFromAddress), scriptBuf); + } + }); + }); +} + +utxolib.getNetworkList().forEach((network) => { + runTest(network); +}); diff --git a/packages/wasm-utxo/test/fixtures/address/bitcoin.json b/packages/wasm-utxo/test/fixtures/address/bitcoin.json new file mode 100644 index 0000000..4c40b8f --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/bitcoin.json @@ -0,0 +1,142 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "13kMLLGzuKZ114tLzujpDShco7wyGLAfbN" + ], + [ + "p2wkh", + "00141e231c7f9b3415daaa53ee5a7e12e120f00ec212", + "bc1qrc33clumxs2a42jnaed8uyhpyrcqassje6kugr" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "33GaWMLihvhqWJ9YNd7xo8diSh6otdfi5w" + ], + [ + "p2shP2wsh", + "a9140c4e25aa3282fa35888f5e1eedb876265328312587", + "32p5ebx2LtubNbR2dZQDY2JN6B7FqRiBFz" + ], + [ + "p2wsh", + "00208bb2ef4181b60abe68b4c9cdc44c92e73bbb17fa2611e7e5b60d794794a1c94d", + "bc1q3wew7svpkc9tu695e8xugnyjuuamk9l6ycg70edkp4u5099pe9xs92xnuj" + ], + [ + "p2tr", + "5120c4beea12923f95c32976d3d1ca7d5490aa3ea28f96d5feacc8ecc28819925eb5", + "bc1pcjlw5y5j872ux2tk60gu5l25jz4rag50jm2latxganpgsxvjt66sr7hzvl" + ], + [ + "p2trMusig2", + "51205f98a79a3f750b250bee5bbdca0705db0ec8621f1bda91a083536a8a8bd6b6ed", + "bc1pt7v20x3lw59j2zlwtw7u5pc9mv8vscslr0dfrgyr2d4g4z7kkmksklpjys" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "1EHWYySCDPN536VzrSvYxKDEx8K2v2yabh" + ], + [ + "p2wkh", + "001491b8f56f155030f74259be43dff4d94a6258d84a", + "bc1qjxu02mc42qc0wsjehepalaxeff393kz2mzwu70" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "3MDt56c1ME4Vi8aYP9DJdhWngxo3yDxGJb" + ], + [ + "p2shP2wsh", + "a914696cab5f237c954fc1fade8c6b234fe93e0e80f287", + "3BJT4AnxLJqvhpdVWDcPBjQwTCTbb7dKP5" + ], + [ + "p2wsh", + "0020a0f0ee4bfe6a5393ffb952c17425566c8a6a11600450818afebb68c3c0c18b09", + "bc1q5rcwujl7dffe8lae2tqhgf2kdj9x5ytqq3ggrzh7hd5v8sxp3vysfeeh2y" + ], + [ + "p2tr", + "5120bc26f82eb59de4f345c94d7a307b022e4da476339f749d4af7e241ab9ea9d804", + "bc1phsn0st44nhj0x3wff4arq7cz9ex6ga3nna6f6jhhufq6h84fmqzqnqwqrm" + ], + [ + "p2trMusig2", + "51205709885a355f7fa37976e8aa16607d831df05107a970ae9cd4b25e401741d2df", + "bc1p2uycsk34tal6x7tkaz4pvcrasvwlq5g849c2a8x5kf0yq96p6t0s3s4p5l" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "1uzQ2jGowagtMQ6cppZCA5Zxik7whPp4H" + ], + [ + "p2wkh", + "00140a058aec7588fca80070436b020c352c2891b680", + "bc1qpgzc4mr43r72sqrsgd4syrp49s5frd5qq7et93" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "32BkhNhA8a7byNxgz6RR3jrG8T1mCXqiC8" + ], + [ + "p2shP2wsh", + "a914f7db4f654f1211a63165cfdaf1170e96d433bc1387", + "3QHZYrvMnq7PBT6CP3fcaNeWHouz1DHTRF" + ], + [ + "p2wsh", + "00204d240cf4a05921cb8a24c6e373488ef8a038782ba75cd60dbe47ed05e5d940b3", + "bc1qf5jqea9qtysuhz3ycm3hxjywlzsrs7pt5awdvrd7glkstewegzesgw848h" + ], + [ + "p2tr", + "512039c67518d173820cc2b97bf6eb873ede5426ec1e6fd2d5ff14c707d44c1c044e", + "bc1p88r82xx3wwpqes4e00mwhpe7me2zdmq7dlfdtlc5curagnquq38qef59st" + ], + [ + "p2trMusig2", + "51208a823980a8c1b9cd182fa7f6771e726f3d2ff16c550ccdb95a26e25bc40f5e84", + "bc1p32prnq9gcxuu6xp05lm8w8njdu7jlutv25xvmw26ym39h3q0t6zq7d0gly" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "19FcKrniJeAg17ws3P3f11F486XH8rwL6s" + ], + [ + "p2wkh", + "00145a8451539186feb4578b4f5613df6991e3078230", + "bc1qt2z9z5u3smltg4utfatp8hmfj83s0q3s62ygc4" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "3FZavdFukrNR5aJcL7dmbc18tZ9tBkVtqK" + ], + [ + "p2shP2wsh", + "a91493f1dd87104175795a1e37f5245461827237a05787", + "3FBGzqkmANdn94Q1VzrXo2YRhNMwi7sWMB" + ], + [ + "p2wsh", + "0020161f1f0478c1649e1b1bf5eba467a27ba621d8a0f69fe7f46a27c3bb0f628a54", + "bc1qzc037prcc9jfuxcm7h46geaz0wnzrk9q76070ar2ylpmkrmz3f2qy78ms6" + ], + [ + "p2tr", + "5120a851285f56e16e91512f54b76e72c1cb2da34ae4de457702d095d3135c77fbfb", + "bc1p4pgjsh6ku9hfz5f02jmkuukpevk6xjhymezhwqksjhf3xhrhl0asz42wc0" + ], + [ + "p2trMusig2", + "512085078b6ce45af8c4dea63248a5fae283d8edcca75f186395b237706c4bb42a36", + "bc1ps5rckm8yttuvfh4xxfy2t7hzs0vwmn98tuvx89djxacxcja59gmq8lx0zu" + ] +] diff --git a/packages/wasm-utxo/test/fixtures/address/bitcoinPublicSignet.json b/packages/wasm-utxo/test/fixtures/address/bitcoinPublicSignet.json new file mode 100644 index 0000000..4c0b817 --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/bitcoinPublicSignet.json @@ -0,0 +1,142 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "miGJdPMyiLzFnBMxiUiC3Muwf7YgBFfJfp" + ], + [ + "p2wkh", + "00141e231c7f9b3415daaa53ee5a7e12e120f00ec212", + "tb1qrc33clumxs2a42jnaed8uyhpyrcqassjnud0ns" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "2Mtpna6GkKPDBi5n63kjqR5cyf3JybU8XmH" + ], + [ + "p2shP2wsh", + "a9140c4e25aa3282fa35888f5e1eedb876265328312587", + "2MtNHiLt3xMQwaP3aJh269yHdJXKRf13yZX" + ], + [ + "p2wsh", + "00208bb2ef4181b60abe68b4c9cdc44c92e73bbb17fa2611e7e5b60d794794a1c94d", + "tb1q3wew7svpkc9tu695e8xugnyjuuamk9l6ycg70edkp4u5099pe9xsjzsuxa" + ], + [ + "p2tr", + "5120c4beea12923f95c32976d3d1ca7d5490aa3ea28f96d5feacc8ecc28819925eb5", + "tb1pcjlw5y5j872ux2tk60gu5l25jz4rag50jm2latxganpgsxvjt66s5kpdks" + ], + [ + "p2trMusig2", + "51205f98a79a3f750b250bee5bbdca0705db0ec8621f1bda91a083536a8a8bd6b6ed", + "tb1pt7v20x3lw59j2zlwtw7u5pc9mv8vscslr0dfrgyr2d4g4z7kkmksphha7l" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "mtoTr2XB2QoKpCyca1tvnERZp7ujv9jJrC" + ], + [ + "p2wkh", + "001491b8f56f155030f74259be43dff4d94a6258d84a", + "tb1qjxu02mc42qc0wsjehepalaxeff393kz23y409u" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "2NCn68qY2xgZquvD64GqBFeW3uK1Dqn2PA4" + ], + [ + "p2shP2wsh", + "a914696cab5f237c954fc1fade8c6b234fe93e0e80f287", + "2N2rf7uiywmMGucG3BMEFogQCfYfmK1DNY9" + ], + [ + "p2wsh", + "0020a0f0ee4bfe6a5393ffb952c17425566c8a6a11600450818afebb68c3c0c18b09", + "tb1q5rcwujl7dffe8lae2tqhgf2kdj9x5ytqq3ggrzh7hd5v8sxp3vys730cst" + ], + [ + "p2tr", + "5120bc26f82eb59de4f345c94d7a307b022e4da476339f749d4af7e241ab9ea9d804", + "tb1phsn0st44nhj0x3wff4arq7cz9ex6ga3nna6f6jhhufq6h84fmqzqygc0e5" + ], + [ + "p2trMusig2", + "51205709885a355f7fa37976e8aa16607d831df05107a970ae9cd4b25e401741d2df", + "tb1p2uycsk34tal6x7tkaz4pvcrasvwlq5g849c2a8x5kf0yq96p6t0sxcrwws" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "mgRwh5pFcy1wfTsiLPnw25HtpiLpvCtj6M" + ], + [ + "p2wkh", + "00140a058aec7588fca80070436b020c352c2891b680", + "tb1qpgzc4mr43r72sqrsgd4syrp49s5frd5q2czc7z" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "2Msjxm7dBk2cxBAbEfE3HfgqXLoDw5wBFpQ" + ], + [ + "p2shP2wsh", + "a914f7db4f654f1211a63165cfdaf1170e96d433bc1387", + "2NFqmcbrPQHcjPEik4BHVCKdmWA89nmYpDF" + ], + [ + "p2wsh", + "00204d240cf4a05921cb8a24c6e373488ef8a038782ba75cd60dbe47ed05e5d940b3", + "tb1qf5jqea9qtysuhz3ycm3hxjywlzsrs7pt5awdvrd7glkstewegzeslx36ac" + ], + [ + "p2tr", + "512039c67518d173820cc2b97bf6eb873ede5426ec1e6fd2d5ff14c707d44c1c044e", + "tb1p88r82xx3wwpqes4e00mwhpe7me2zdmq7dlfdtlc5curagnquq38qwpz22y" + ], + [ + "p2trMusig2", + "51208a823980a8c1b9cd182fa7f6771e726f3d2ff16c550ccdb95a26e25bc40f5e84", + "tb1p32prnq9gcxuu6xp05lm8w8njdu7jlutv25xvmw26ym39h3q0t6zqf9e89t" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "momZcush7fbvnERUkx22pvTNz67z3iDSj8" + ], + [ + "p2wkh", + "00145a8451539186feb4578b4f5613df6991e3078230", + "tb1qt2z9z5u3smltg4utfatp8hmfj83s0q3ssvlmrx" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "2N77nzNBwNJsmHMwA1FFeDYzQ6uN3yhET3E" + ], + [ + "p2shP2wsh", + "a91493f1dd87104175795a1e37f5245461827237a05787", + "2N6jV4agnmq98Lr2ZB8UQQyXguia7SyGKqG" + ], + [ + "p2wsh", + "0020161f1f0478c1649e1b1bf5eba467a27ba621d8a0f69fe7f46a27c3bb0f628a54", + "tb1qzc037prcc9jfuxcm7h46geaz0wnzrk9q76070ar2ylpmkrmz3f2qnk3524" + ], + [ + "p2tr", + "5120a851285f56e16e91512f54b76e72c1cb2da34ae4de457702d095d3135c77fbfb", + "tb1p4pgjsh6ku9hfz5f02jmkuukpevk6xjhymezhwqksjhf3xhrhl0as4aupzq" + ], + [ + "p2trMusig2", + "512085078b6ce45af8c4dea63248a5fae283d8edcca75f186395b237706c4bb42a36", + "tb1ps5rckm8yttuvfh4xxfy2t7hzs0vwmn98tuvx89djxacxcja59gmqshsqcn" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/bitcoincash-cashaddr.json b/packages/wasm-utxo/test/fixtures/address/bitcoincash-cashaddr.json new file mode 100644 index 0000000..d4fb124 --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/bitcoincash-cashaddr.json @@ -0,0 +1,42 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "bitcoincash:qq0zx8rlnv6ptk4220h95lsjuys0qrkzzgx0n4szz0" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "bitcoincash:pqg4zrf9vpu5k0khhae5hs8qxrnsund595cuvk22a2" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "bitcoincash:qzgm3at0z4grpa6ztxly8hl5m99xykxcfglphmk73k" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "bitcoincash:prtypwksltlza6kfls8q7z0m3xgxvf37hux6f5hfnh" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "bitcoincash:qq9qtzhvwky0e2qqwppkkqsvx5kz3ydksqr6vl0rmd" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "bitcoincash:pqzk7ksuqllr35n7249c30v90ajtm28tdu8rv0zecm" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "bitcoincash:qpdgg52njxr0adzh3d84vy7ldxg7xpuzxqcu40tl8d" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "bitcoincash:pzvzn76pu0ptea4pvscs57kt7zkucrp7uynqksr2kp" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/bitcoincash.json b/packages/wasm-utxo/test/fixtures/address/bitcoincash.json new file mode 100644 index 0000000..132bb0f --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/bitcoincash.json @@ -0,0 +1,42 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "13kMLLGzuKZ114tLzujpDShco7wyGLAfbN" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "33GaWMLihvhqWJ9YNd7xo8diSh6otdfi5w" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "1EHWYySCDPN536VzrSvYxKDEx8K2v2yabh" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "3MDt56c1ME4Vi8aYP9DJdhWngxo3yDxGJb" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "1uzQ2jGowagtMQ6cppZCA5Zxik7whPp4H" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "32BkhNhA8a7byNxgz6RR3jrG8T1mCXqiC8" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "19FcKrniJeAg17ws3P3f11F486XH8rwL6s" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "3FZavdFukrNR5aJcL7dmbc18tZ9tBkVtqK" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/bitcoincashTestnet-cashaddr.json b/packages/wasm-utxo/test/fixtures/address/bitcoincashTestnet-cashaddr.json new file mode 100644 index 0000000..70a893a --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/bitcoincashTestnet-cashaddr.json @@ -0,0 +1,42 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "bchtest:qq0zx8rlnv6ptk4220h95lsjuys0qrkzzgzahjj49n" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "bchtest:pqg4zrf9vpu5k0khhae5hs8qxrnsund595uwg3ga6k" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "bchtest:qzgm3at0z4grpa6ztxly8hl5m99xykxcfgmnnu5fk2" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "bchtest:prtypwksltlza6kfls8q7z0m3xgxvf37huzgdn475t" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "bchtest:qq9qtzhvwky0e2qqwppkkqsvx5kz3ydksq8ggcd5u3" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "bchtest:pqzk7ksuqllr35n7249c30v90ajtm28tdur3ggqwl8" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "bchtest:qpdgg52njxr0adzh3d84vy7ldxg7xpuzxquw3gfgq3" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "bchtest:pzvzn76pu0ptea4pvscs57kt7zkucrp7uyhjjhpa3a" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/bitcoincashTestnet.json b/packages/wasm-utxo/test/fixtures/address/bitcoincashTestnet.json new file mode 100644 index 0000000..0778afd --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/bitcoincashTestnet.json @@ -0,0 +1,42 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "miGJdPMyiLzFnBMxiUiC3Muwf7YgBFfJfp" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "2Mtpna6GkKPDBi5n63kjqR5cyf3JybU8XmH" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "mtoTr2XB2QoKpCyca1tvnERZp7ujv9jJrC" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "2NCn68qY2xgZquvD64GqBFeW3uK1Dqn2PA4" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "mgRwh5pFcy1wfTsiLPnw25HtpiLpvCtj6M" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "2Msjxm7dBk2cxBAbEfE3HfgqXLoDw5wBFpQ" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "momZcush7fbvnERUkx22pvTNz67z3iDSj8" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "2N77nzNBwNJsmHMwA1FFeDYzQ6uN3yhET3E" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/bitcoingold.json b/packages/wasm-utxo/test/fixtures/address/bitcoingold.json new file mode 100644 index 0000000..074cd68 --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/bitcoingold.json @@ -0,0 +1,102 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "GLbGkTbwtBAJ5YBdvrPveD3WiHjpG7S4Mt" + ], + [ + "p2wkh", + "00141e231c7f9b3415daaa53ee5a7e12e120f00ec212", + "btg1qrc33clumxs2a42jnaed8uyhpyrcqassj0nseat" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "AHMSEJhuVB3cE6f6pB7hXPXsmmjnhUpmV1" + ], + [ + "p2shP2wsh", + "a9140c4e25aa3282fa35888f5e1eedb876265328312587", + "AGtwNZKD89FN6Pvb57PxGHCXRFkEbVaFKg" + ], + [ + "p2wsh", + "00208bb2ef4181b60abe68b4c9cdc44c92e73bbb17fa2611e7e5b60d794794a1c94d", + "btg1q3wew7svpkc9tu695e8xugnyjuuamk9l6ycg70edkp4u5099pe9xst2zwvc" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "GX8Ry6m9CEyN7ZoHnPafP5Z8sJ6szX4uWN" + ], + [ + "p2wkh", + "001491b8f56f155030f74259be43dff4d94a6258d84a", + "btg1qjxu02mc42qc0wsjehepalaxeff393kz2dtget8" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "AbJjo3yC8UQGRw66phD3MxQx23S2n2W9HA" + ], + [ + "p2shP2wsh", + "a914696cab5f237c954fc1fade8c6b234fe93e0e80f287", + "ARPJn8A97ZBhRd93wmc7uzK6nH6aHVQm5g" + ], + [ + "p2wsh", + "0020a0f0ee4bfe6a5393ffb952c17425566c8a6a11600450818afebb68c3c0c18b09", + "btg1q5rcwujl7dffe8lae2tqhgf2kdj9x5ytqq3ggrzh7hd5v8sxp3vys8ea26w" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "GJkupA4DnoByxphPYmUfcvRTstXxw5dTap" + ], + [ + "p2wkh", + "00140a058aec7588fca80070436b020c352c2891b680", + "btg1qpgzc4mr43r72sqrsgd4syrp49s5frd5qkhlwse" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "AGGcRL4LupTNhBUFReR9mzkRTXek4uG92P" + ], + [ + "p2shP2wsh", + "a914f7db4f654f1211a63165cfdaf1170e96d433bc1387", + "AeNRGpHYa5T9uFbkpbfMJdYfctYxm9avkx" + ], + [ + "p2wsh", + "00204d240cf4a05921cb8a24c6e373488ef8a038782ba75cd60dbe47ed05e5d940b3", + "btg1qf5jqea9qtysuhz3ycm3hxjywlzsrs7pt5awdvrd7glkstewegzesxwrgha" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "GS6Xjz7fHVmy5bF9yKhmRmax3GK8FJTTkd" + ], + [ + "p2wkh", + "00145a8451539186feb4578b4f5613df6991e3078230", + "btg1qt2z9z5u3smltg4utfatp8hmfj83s0q3svrzdda" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "AVeSead6Y6iBoNpAmfdWKruJDdnrwK1Ybo" + ], + [ + "p2shP2wsh", + "a91493f1dd87104175795a1e37f5245461827237a05787", + "AVG8io7wwcyYrruZwYrGXHSb2SzvZJSvMt" + ], + [ + "p2wsh", + "0020161f1f0478c1649e1b1bf5eba467a27ba621d8a0f69fe7f46a27c3bb0f628a54", + "btg1qzc037prcc9jfuxcm7h46geaz0wnzrk9q76070ar2ylpmkrmz3f2q27rxqs" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/bitcoingoldTestnet.json b/packages/wasm-utxo/test/fixtures/address/bitcoingoldTestnet.json new file mode 100644 index 0000000..a04eb9a --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/bitcoingoldTestnet.json @@ -0,0 +1,102 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "miGJdPMyiLzFnBMxiUiC3Muwf7YgBFfJfp" + ], + [ + "p2wkh", + "00141e231c7f9b3415daaa53ee5a7e12e120f00ec212", + "tbtg1qrc33clumxs2a42jnaed8uyhpyrcqassjcpnswp" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "2Mtpna6GkKPDBi5n63kjqR5cyf3JybU8XmH" + ], + [ + "p2shP2wsh", + "a9140c4e25aa3282fa35888f5e1eedb876265328312587", + "2MtNHiLt3xMQwaP3aJh269yHdJXKRf13yZX" + ], + [ + "p2wsh", + "00208bb2ef4181b60abe68b4c9cdc44c92e73bbb17fa2611e7e5b60d794794a1c94d", + "tbtg1q3wew7svpkc9tu695e8xugnyjuuamk9l6ycg70edkp4u5099pe9xsq9xsnd" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "mtoTr2XB2QoKpCyca1tvnERZp7ujv9jJrC" + ], + [ + "p2wkh", + "001491b8f56f155030f74259be43dff4d94a6258d84a", + "tbtg1qjxu02mc42qc0wsjehepalaxeff393kz26etscd" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "2NCn68qY2xgZquvD64GqBFeW3uK1Dqn2PA4" + ], + [ + "p2shP2wsh", + "a914696cab5f237c954fc1fade8c6b234fe93e0e80f287", + "2N2rf7uiywmMGucG3BMEFogQCfYfmK1DNY9" + ], + [ + "p2wsh", + "0020a0f0ee4bfe6a5393ffb952c17425566c8a6a11600450818afebb68c3c0c18b09", + "tbtg1q5rcwujl7dffe8lae2tqhgf2kdj9x5ytqq3ggrzh7hd5v8sxp3vysvke59m" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "mgRwh5pFcy1wfTsiLPnw25HtpiLpvCtj6M" + ], + [ + "p2wkh", + "00140a058aec7588fca80070436b020c352c2891b680", + "tbtg1qpgzc4mr43r72sqrsgd4syrp49s5frd5qp9u8rn" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "2Msjxm7dBk2cxBAbEfE3HfgqXLoDw5wBFpQ" + ], + [ + "p2shP2wsh", + "a914f7db4f654f1211a63165cfdaf1170e96d433bc1387", + "2NFqmcbrPQHcjPEik4BHVCKdmWA89nmYpDF" + ], + [ + "p2wsh", + "00204d240cf4a05921cb8a24c6e373488ef8a038782ba75cd60dbe47ed05e5d940b3", + "tbtg1qf5jqea9qtysuhz3ycm3hxjywlzsrs7pt5awdvrd7glkstewegzesdp8kgg" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "momZcush7fbvnERUkx22pvTNz67z3iDSj8" + ], + [ + "p2wkh", + "00145a8451539186feb4578b4f5613df6991e3078230", + "tbtg1qt2z9z5u3smltg4utfatp8hmfj83s0q3sm3py7h" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "2N77nzNBwNJsmHMwA1FFeDYzQ6uN3yhET3E" + ], + [ + "p2shP2wsh", + "a91493f1dd87104175795a1e37f5245461827237a05787", + "2N6jV4agnmq98Lr2ZB8UQQyXguia7SyGKqG" + ], + [ + "p2wsh", + "0020161f1f0478c1649e1b1bf5eba467a27ba621d8a0f69fe7f46a27c3bb0f628a54", + "tbtg1qzc037prcc9jfuxcm7h46geaz0wnzrk9q76070ar2ylpmkrmz3f2qp38cl9" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/bitcoinsv.json b/packages/wasm-utxo/test/fixtures/address/bitcoinsv.json new file mode 100644 index 0000000..132bb0f --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/bitcoinsv.json @@ -0,0 +1,42 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "13kMLLGzuKZ114tLzujpDShco7wyGLAfbN" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "33GaWMLihvhqWJ9YNd7xo8diSh6otdfi5w" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "1EHWYySCDPN536VzrSvYxKDEx8K2v2yabh" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "3MDt56c1ME4Vi8aYP9DJdhWngxo3yDxGJb" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "1uzQ2jGowagtMQ6cppZCA5Zxik7whPp4H" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "32BkhNhA8a7byNxgz6RR3jrG8T1mCXqiC8" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "19FcKrniJeAg17ws3P3f11F486XH8rwL6s" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "3FZavdFukrNR5aJcL7dmbc18tZ9tBkVtqK" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/bitcoinsvTestnet.json b/packages/wasm-utxo/test/fixtures/address/bitcoinsvTestnet.json new file mode 100644 index 0000000..0778afd --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/bitcoinsvTestnet.json @@ -0,0 +1,42 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "miGJdPMyiLzFnBMxiUiC3Muwf7YgBFfJfp" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "2Mtpna6GkKPDBi5n63kjqR5cyf3JybU8XmH" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "mtoTr2XB2QoKpCyca1tvnERZp7ujv9jJrC" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "2NCn68qY2xgZquvD64GqBFeW3uK1Dqn2PA4" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "mgRwh5pFcy1wfTsiLPnw25HtpiLpvCtj6M" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "2Msjxm7dBk2cxBAbEfE3HfgqXLoDw5wBFpQ" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "momZcush7fbvnERUkx22pvTNz67z3iDSj8" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "2N77nzNBwNJsmHMwA1FFeDYzQ6uN3yhET3E" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/dash.json b/packages/wasm-utxo/test/fixtures/address/dash.json new file mode 100644 index 0000000..d935d3e --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/dash.json @@ -0,0 +1,42 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "XdSCAavts2mbA1Uvro434yPQdTXfMJZWBt" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "7TzDLYctWuoUW4gVeEnU8WdNNEwBeEYuHV" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "XoyMPE66B6afC36aiLEmoqu2nTtj1x8D89" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "7mwWuHtBADA8hu7Veksoy5WScWdRpCKCAS" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "XbbqEHPAmeoH3HzgUi8n3gmMo4KowSHveT" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "7SuPXZyKwZDEy9VeFi5vP7qv3zr97LJc1q" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "XiwTA7ScGMPGA4YSuGMsrXvqxS6yEVVGav" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "7gHDkpY5ZqU45LqZbjJGvyznp6zFvsG2xC" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/dashTest.json b/packages/wasm-utxo/test/fixtures/address/dashTest.json new file mode 100644 index 0000000..762140e --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/dashTest.json @@ -0,0 +1,42 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "yP4oBY1LJaRfVkQUReNS6zokuk22sLH9Bc" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "8g12HsWkeTC6xN6kiVnRatSjFki1nMLQzC" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "yZbxQBAXceEjXn28HBZAqsKP4kP6XvwAAC" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "8yxKrcn3HkYmACXkj1smRTKoW2QFzxLCkQ" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "yMESFETcDCTMP2vE3ZTB5iBi5LpBUh72we" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "8evCUtsC56bsRSuuKy5sqVfGwWcyA3irFo" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "yUa4B4X3hu3LVoTzU7gGtZMCEibLgzNHFL" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "8tJ2i9RwhNrgXeFpfzJEPMp9hcm66Drone" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/dogecoin.json b/packages/wasm-utxo/test/fixtures/address/dogecoin.json new file mode 100644 index 0000000..cd8cdb7 --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/dogecoin.json @@ -0,0 +1,42 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "D7tSsbDeCjTHY54wjVjNmCsDgFgGecewd1" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "9t1qFCQcmzajQfX1nknP3GG69GUqz3M1v2" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "DJRc6ENqWoGMa6gbb2v7W5NqqG3LGrFax4" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "ABy8owfuRHwPcVx1oGsisq9APYB64RuazD" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "D645wHfv7MUyRMahMQp7jvFAqrURJbZ3Np" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "9rw1SDm4CdzVskLAQE5qHsUdq2PoHwx5jJ" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "DDPhs7jMc44xY88Tmy3DYmQf1EFaX1xtNz" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "A6JqfUKopvFJywg5kFJBqjdWb8XvAqiXJb" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/dogecoinTest.json b/packages/wasm-utxo/test/fixtures/address/dogecoinTest.json new file mode 100644 index 0000000..ed2211e --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/dogecoinTest.json @@ -0,0 +1,42 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "nWwWbbxZ8hv1R3e8mKNq1cTWv84ZcpsDEZ" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "2Mtpna6GkKPDBi5n63kjqR5cyf3JybU8XmH" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "nhUfpF7kSmj5T5FncrZZkUy958RdLpKc9w" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "2NCn68qY2xgZquvD64GqBFeW3uK1Dqn2PA4" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "nV79fJQq3KwhJL9tPETZzKqU5iriM7HhkF" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "2Msjxm7dBk2cxBAbEfE3HfgqXLoDw5wBFpQ" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "ncSmb8UGY2XgR6heongfoAzxF6dsUZpANa" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "2N77nzNBwNJsmHMwA1FFeDYzQ6uN3yhET3E" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/ecash-cashaddr.json b/packages/wasm-utxo/test/fixtures/address/ecash-cashaddr.json new file mode 100644 index 0000000..5912992 --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/ecash-cashaddr.json @@ -0,0 +1,42 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "ecash:qq0zx8rlnv6ptk4220h95lsjuys0qrkzzglz87tcyc" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "ecash:pqg4zrf9vpu5k0khhae5hs8qxrnsund595p3ca3sma" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "ecash:qzgm3at0z4grpa6ztxly8hl5m99xykxcfgxvrsdyhp" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "ecash:prtypwksltlza6kfls8q7z0m3xgxvf37hulhalvn4q" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "ecash:qq9qtzhvwky0e2qqwppkkqsvx5kz3ydksq6hc55ea6" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "ecash:pqzk7ksuqllr35n7249c30v90ajtm28tdu7wcyer7v" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "ecash:qpdgg52njxr0adzh3d84vy7ldxg7xpuzxqp3pys9p6" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "ecash:pzvzn76pu0ptea4pvscs57kt7zkucrp7uy2dzmcssk" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/ecash.json b/packages/wasm-utxo/test/fixtures/address/ecash.json new file mode 100644 index 0000000..132bb0f --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/ecash.json @@ -0,0 +1,42 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "13kMLLGzuKZ114tLzujpDShco7wyGLAfbN" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "33GaWMLihvhqWJ9YNd7xo8diSh6otdfi5w" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "1EHWYySCDPN536VzrSvYxKDEx8K2v2yabh" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "3MDt56c1ME4Vi8aYP9DJdhWngxo3yDxGJb" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "1uzQ2jGowagtMQ6cppZCA5Zxik7whPp4H" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "32BkhNhA8a7byNxgz6RR3jrG8T1mCXqiC8" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "19FcKrniJeAg17ws3P3f11F486XH8rwL6s" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "3FZavdFukrNR5aJcL7dmbc18tZ9tBkVtqK" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/ecashTest-cashaddr.json b/packages/wasm-utxo/test/fixtures/address/ecashTest-cashaddr.json new file mode 100644 index 0000000..b6473ae --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/ecashTest-cashaddr.json @@ -0,0 +1,10 @@ +[ + ["p2pkh", "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", "ectest:qq0zx8rlnv6ptk4220h95lsjuys0qrkzzgefe7v48f"], + ["p2sh", "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", "ectest:pqg4zrf9vpu5k0khhae5hs8qxrnsund59586xakacv"], + ["p2pkh", "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", "ectest:qzgm3at0z4grpa6ztxly8hl5m99xykxcfgq8as2f5s"], + ["p2sh", "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", "ectest:prtypwksltlza6kfls8q7z0m3xgxvf37hueurlt7k3"], + ["p2pkh", "76a9140a058aec7588fca80070436b020c352c2891b68088ac", "ectest:qq9qtzhvwky0e2qqwppkkqsvx5kz3ydksquux5n57t"], + ["p2sh", "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", "ectest:pqzk7ksuqllr35n7249c30v90ajtm28tduc9xy7waa"], + ["p2pkh", "76a9145a8451539186feb4578b4f5613df6991e307823088ac", "ectest:qpdgg52njxr0adzh3d84vy7ldxg7xpuzxq86lyhgzt"], + ["p2sh", "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", "ectest:pzvzn76pu0ptea4pvscs57kt7zkucrp7uyvxumlan8"] +] diff --git a/packages/wasm-utxo/test/fixtures/address/ecashTest.json b/packages/wasm-utxo/test/fixtures/address/ecashTest.json new file mode 100644 index 0000000..0778afd --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/ecashTest.json @@ -0,0 +1,42 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "miGJdPMyiLzFnBMxiUiC3Muwf7YgBFfJfp" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "2Mtpna6GkKPDBi5n63kjqR5cyf3JybU8XmH" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "mtoTr2XB2QoKpCyca1tvnERZp7ujv9jJrC" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "2NCn68qY2xgZquvD64GqBFeW3uK1Dqn2PA4" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "mgRwh5pFcy1wfTsiLPnw25HtpiLpvCtj6M" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "2Msjxm7dBk2cxBAbEfE3HfgqXLoDw5wBFpQ" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "momZcush7fbvnERUkx22pvTNz67z3iDSj8" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "2N77nzNBwNJsmHMwA1FFeDYzQ6uN3yhET3E" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/ecashTestnet-cashaddr.json b/packages/wasm-utxo/test/fixtures/address/ecashTestnet-cashaddr.json new file mode 100644 index 0000000..b6473ae --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/ecashTestnet-cashaddr.json @@ -0,0 +1,10 @@ +[ + ["p2pkh", "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", "ectest:qq0zx8rlnv6ptk4220h95lsjuys0qrkzzgefe7v48f"], + ["p2sh", "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", "ectest:pqg4zrf9vpu5k0khhae5hs8qxrnsund59586xakacv"], + ["p2pkh", "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", "ectest:qzgm3at0z4grpa6ztxly8hl5m99xykxcfgq8as2f5s"], + ["p2sh", "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", "ectest:prtypwksltlza6kfls8q7z0m3xgxvf37hueurlt7k3"], + ["p2pkh", "76a9140a058aec7588fca80070436b020c352c2891b68088ac", "ectest:qq9qtzhvwky0e2qqwppkkqsvx5kz3ydksquux5n57t"], + ["p2sh", "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", "ectest:pqzk7ksuqllr35n7249c30v90ajtm28tduc9xy7waa"], + ["p2pkh", "76a9145a8451539186feb4578b4f5613df6991e307823088ac", "ectest:qpdgg52njxr0adzh3d84vy7ldxg7xpuzxq86lyhgzt"], + ["p2sh", "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", "ectest:pzvzn76pu0ptea4pvscs57kt7zkucrp7uyvxumlan8"] +] diff --git a/packages/wasm-utxo/test/fixtures/address/ecashTestnet.json b/packages/wasm-utxo/test/fixtures/address/ecashTestnet.json new file mode 100644 index 0000000..0778afd --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/ecashTestnet.json @@ -0,0 +1,42 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "miGJdPMyiLzFnBMxiUiC3Muwf7YgBFfJfp" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "2Mtpna6GkKPDBi5n63kjqR5cyf3JybU8XmH" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "mtoTr2XB2QoKpCyca1tvnERZp7ujv9jJrC" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "2NCn68qY2xgZquvD64GqBFeW3uK1Dqn2PA4" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "mgRwh5pFcy1wfTsiLPnw25HtpiLpvCtj6M" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "2Msjxm7dBk2cxBAbEfE3HfgqXLoDw5wBFpQ" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "momZcush7fbvnERUkx22pvTNz67z3iDSj8" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "2N77nzNBwNJsmHMwA1FFeDYzQ6uN3yhET3E" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/litecoin.json b/packages/wasm-utxo/test/fixtures/address/litecoin.json new file mode 100644 index 0000000..1bbab73 --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/litecoin.json @@ -0,0 +1,102 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "LMyJbYapyyo4FsaWB3j7VTmP1LKFNdGh9o" + ], + [ + "p2wkh", + "00141e231c7f9b3415daaa53ee5a7e12e120f00ec212", + "ltc1qrc33clumxs2a42jnaed8uyhpyrcqassjaxvcsn" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "M9UipEkgf3ZGJoRSUW7Jcmt7mPhFnzn9Xu" + ], + [ + "p2shP2wsh", + "a9140c4e25aa3282fa35888f5e1eedb876265328312587", + "M92DxVMzJ1m2B6gvjSPZMfYmQshhrPuCij" + ], + [ + "p2wsh", + "00208bb2ef4181b60abe68b4c9cdc44c92e73bbb17fa2611e7e5b60d794794a1c94d", + "ltc1q3wew7svpkc9tu695e8xugnyjuuamk9l6ycg70edkp4u5099pe9xsxwgrxh" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "LYWTpBk2J3c8HuCA2aurELH1ALgK4jt4YM" + ], + [ + "p2wkh", + "001491b8f56f155030f74259be43dff4d94a6258d84a", + "ltc1qjxu02mc42qc0wsjehepalaxeff393kz2l75cxl" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "MTS2Nz1yJLuvWdrSV2CeTLmC1fPVy19eXE" + ], + [ + "p2shP2wsh", + "a914696cab5f237c954fc1fade8c6b234fe93e0e80f287", + "MHWbN4CvHRhMWKuPc6bj1NfLmu43VKL7H4" + ], + [ + "p2wsh", + "0020a0f0ee4bfe6a5393ffb952c17425566c8a6a11600450818afebb68c3c0c18b09", + "ltc1q5rcwujl7dffe8lae2tqhgf2kdj9x5ytqq3ggrzh7hd5v8sxp3vys2ah8sp" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "LL8wfF36tbpk9A6FnxorUB9LAw7Q6Y7RQJ" + ], + [ + "p2wkh", + "00140a058aec7588fca80070436b020c352c2891b680", + "ltc1qpgzc4mr43r72sqrsgd4syrp49s5frd5qyzr0ap" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "M8Pu1G785gy2mtEb5yQksP6fT9cDFNS1MV" + ], + [ + "p2shP2wsh", + "a914f7db4f654f1211a63165cfdaf1170e96d433bc1387", + "MWVhrkLKjwxoyxN6UvexQ1tucWWRyjvo1k" + ], + [ + "p2wsh", + "00204d240cf4a05921cb8a24c6e373488ef8a038782ba75cd60dbe47ed05e5d940b3", + "ltc1qf5jqea9qtysuhz3ycm3hxjywlzsrs7pt5awdvrd7glkstewegzest2f9aj" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "LTUZb56YPJQjFve2DX2xH2JpLJtZKvbxop" + ], + [ + "p2wkh", + "00145a8451539186feb4578b4f5613df6991e3078230", + "ltc1qt2z9z5u3smltg4utfatp8hmfj83s0q3s7k7vq9" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "MMmjEWfshyDqt5aWRzd7RFFYDFkLAEJgMB" + ], + [ + "p2shP2wsh", + "a91493f1dd87104175795a1e37f5245461827237a05787", + "MMPRJjAj7VVCwZfubsqscfnq24xPi4Te7d" + ], + [ + "p2wsh", + "0020161f1f0478c1649e1b1bf5eba467a27ba621d8a0f69fe7f46a27c3bb0f628a54", + "ltc1qzc037prcc9jfuxcm7h46geaz0wnzrk9q76070ar2ylpmkrmz3f2q86ft2l" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/litecoinTest.json b/packages/wasm-utxo/test/fixtures/address/litecoinTest.json new file mode 100644 index 0000000..7cd0130 --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/litecoinTest.json @@ -0,0 +1,102 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "miGJdPMyiLzFnBMxiUiC3Muwf7YgBFfJfp" + ], + [ + "p2wkh", + "00141e231c7f9b3415daaa53ee5a7e12e120f00ec212", + "tltc1qrc33clumxs2a42jnaed8uyhpyrcqassj2503re" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "QNBYh78zLVGGrGY8frmrVn4QoRkoVru62J" + ], + [ + "p2shP2wsh", + "a9140c4e25aa3282fa35888f5e1eedb876265328312587", + "QMj3qMkHyTU2iZocvo47Efj4SumFWZKfwi" + ], + [ + "p2wsh", + "00208bb2ef4181b60abe68b4c9cdc44c92e73bbb17fa2611e7e5b60d794794a1c94d", + "tltc1q3wew7svpkc9tu695e8xugnyjuuamk9l6ycg70edkp4u5099pe9xsdpvaez" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "mtoTr2XB2QoKpCyca1tvnERZp7ujv9jJrC" + ], + [ + "p2wkh", + "001491b8f56f155030f74259be43dff4d94a6258d84a", + "tltc1qjxu02mc42qc0wsjehepalaxeff393kz2gvh344" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "Qg8rFrQGyncw46y8gNsCLLwV3hT3i78RHD" + ], + [ + "p2shP2wsh", + "a914696cab5f237c954fc1fade8c6b234fe93e0e80f287", + "QWDREvbDxsQN3o25oTGGtNqdow7bC9VVHi" + ], + [ + "p2wsh", + "0020a0f0ee4bfe6a5393ffb952c17425566c8a6a11600450818afebb68c3c0c18b09", + "tltc1q5rcwujl7dffe8lae2tqhgf2kdj9x5ytqq3ggrzh7hd5v8sxp3vyspjne05" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "mgRwh5pFcy1wfTsiLPnw25HtpiLpvCtj6M" + ], + [ + "p2wkh", + "00140a058aec7588fca80070436b020c352c2891b680", + "tltc1qpgzc4mr43r72sqrsgd4syrp49s5frd5qnsqxwt" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "QM6it8VRm8g3KMMHHL5JkPGxVBfkyDHdxM" + ], + [ + "p2shP2wsh", + "a914f7db4f654f1211a63165cfdaf1170e96d433bc1387", + "QjCXjcidRPfpXRUngHKWH25CeYZye1vYqi" + ], + [ + "p2wsh", + "00204d240cf4a05921cb8a24c6e373488ef8a038782ba75cd60dbe47ed05e5d940b3", + "tltc1qf5jqea9qtysuhz3ycm3hxjywlzsrs7pt5awdvrd7glkstewegzesq9dmz8" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "momZcush7fbvnERUkx22pvTNz67z3iDSj8" + ], + [ + "p2wkh", + "00145a8451539186feb4578b4f5613df6991e3078230", + "tltc1qt2z9z5u3smltg4utfatp8hmfj83s0q3sfya9n0" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "QaUZ7P4BPQvrRYhCdMHfJFRqFHosmcNEL4" + ], + [ + "p2shP2wsh", + "a91493f1dd87104175795a1e37f5245461827237a05787", + "Qa6FBbZ2nwCDV2nboEWRVfy8471wNf4u4g" + ], + [ + "p2wsh", + "0020161f1f0478c1649e1b1bf5eba467a27ba621d8a0f69fe7f46a27c3bb0f628a54", + "tltc1qzc037prcc9jfuxcm7h46geaz0wnzrk9q76070ar2ylpmkrmz3f2qv4d442" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/testnet.json b/packages/wasm-utxo/test/fixtures/address/testnet.json new file mode 100644 index 0000000..d71b196 --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/testnet.json @@ -0,0 +1,142 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "miGJdPMyiLzFnBMxiUiC3Muwf7YgBFfJfp" + ], + [ + "p2wkh", + "00141e231c7f9b3415daaa53ee5a7e12e120f00ec212", + "tb1qrc33clumxs2a42jnaed8uyhpyrcqassjnud0ns" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "2Mtpna6GkKPDBi5n63kjqR5cyf3JybU8XmH" + ], + [ + "p2shP2wsh", + "a9140c4e25aa3282fa35888f5e1eedb876265328312587", + "2MtNHiLt3xMQwaP3aJh269yHdJXKRf13yZX" + ], + [ + "p2wsh", + "00208bb2ef4181b60abe68b4c9cdc44c92e73bbb17fa2611e7e5b60d794794a1c94d", + "tb1q3wew7svpkc9tu695e8xugnyjuuamk9l6ycg70edkp4u5099pe9xsjzsuxa" + ], + [ + "p2tr", + "5120c4beea12923f95c32976d3d1ca7d5490aa3ea28f96d5feacc8ecc28819925eb5", + "tb1pcjlw5y5j872ux2tk60gu5l25jz4rag50jm2latxganpgsxvjt66s5kpdks" + ], + [ + "p2trMusig2", + "51205f98a79a3f750b250bee5bbdca0705db0ec8621f1bda91a083536a8a8bd6b6ed", + "tb1pt7v20x3lw59j2zlwtw7u5pc9mv8vscslr0dfrgyr2d4g4z7kkmksphha7l" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "mtoTr2XB2QoKpCyca1tvnERZp7ujv9jJrC" + ], + [ + "p2wkh", + "001491b8f56f155030f74259be43dff4d94a6258d84a", + "tb1qjxu02mc42qc0wsjehepalaxeff393kz23y409u" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "2NCn68qY2xgZquvD64GqBFeW3uK1Dqn2PA4" + ], + [ + "p2shP2wsh", + "a914696cab5f237c954fc1fade8c6b234fe93e0e80f287", + "2N2rf7uiywmMGucG3BMEFogQCfYfmK1DNY9" + ], + [ + "p2wsh", + "0020a0f0ee4bfe6a5393ffb952c17425566c8a6a11600450818afebb68c3c0c18b09", + "tb1q5rcwujl7dffe8lae2tqhgf2kdj9x5ytqq3ggrzh7hd5v8sxp3vys730cst" + ], + [ + "p2tr", + "5120bc26f82eb59de4f345c94d7a307b022e4da476339f749d4af7e241ab9ea9d804", + "tb1phsn0st44nhj0x3wff4arq7cz9ex6ga3nna6f6jhhufq6h84fmqzqygc0e5" + ], + [ + "p2trMusig2", + "51205709885a355f7fa37976e8aa16607d831df05107a970ae9cd4b25e401741d2df", + "tb1p2uycsk34tal6x7tkaz4pvcrasvwlq5g849c2a8x5kf0yq96p6t0sxcrwws" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "mgRwh5pFcy1wfTsiLPnw25HtpiLpvCtj6M" + ], + [ + "p2wkh", + "00140a058aec7588fca80070436b020c352c2891b680", + "tb1qpgzc4mr43r72sqrsgd4syrp49s5frd5q2czc7z" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "2Msjxm7dBk2cxBAbEfE3HfgqXLoDw5wBFpQ" + ], + [ + "p2shP2wsh", + "a914f7db4f654f1211a63165cfdaf1170e96d433bc1387", + "2NFqmcbrPQHcjPEik4BHVCKdmWA89nmYpDF" + ], + [ + "p2wsh", + "00204d240cf4a05921cb8a24c6e373488ef8a038782ba75cd60dbe47ed05e5d940b3", + "tb1qf5jqea9qtysuhz3ycm3hxjywlzsrs7pt5awdvrd7glkstewegzeslx36ac" + ], + [ + "p2tr", + "512039c67518d173820cc2b97bf6eb873ede5426ec1e6fd2d5ff14c707d44c1c044e", + "tb1p88r82xx3wwpqes4e00mwhpe7me2zdmq7dlfdtlc5curagnquq38qwpz22y" + ], + [ + "p2trMusig2", + "51208a823980a8c1b9cd182fa7f6771e726f3d2ff16c550ccdb95a26e25bc40f5e84", + "tb1p32prnq9gcxuu6xp05lm8w8njdu7jlutv25xvmw26ym39h3q0t6zqf9e89t" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "momZcush7fbvnERUkx22pvTNz67z3iDSj8" + ], + [ + "p2wkh", + "00145a8451539186feb4578b4f5613df6991e3078230", + "tb1qt2z9z5u3smltg4utfatp8hmfj83s0q3ssvlmrx" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "2N77nzNBwNJsmHMwA1FFeDYzQ6uN3yhET3E" + ], + [ + "p2shP2wsh", + "a91493f1dd87104175795a1e37f5245461827237a05787", + "2N6jV4agnmq98Lr2ZB8UQQyXguia7SyGKqG" + ], + [ + "p2wsh", + "0020161f1f0478c1649e1b1bf5eba467a27ba621d8a0f69fe7f46a27c3bb0f628a54", + "tb1qzc037prcc9jfuxcm7h46geaz0wnzrk9q76070ar2ylpmkrmz3f2qnk3524" + ], + [ + "p2tr", + "5120a851285f56e16e91512f54b76e72c1cb2da34ae4de457702d095d3135c77fbfb", + "tb1p4pgjsh6ku9hfz5f02jmkuukpevk6xjhymezhwqksjhf3xhrhl0as4aupzq" + ], + [ + "p2trMusig2", + "512085078b6ce45af8c4dea63248a5fae283d8edcca75f186395b237706c4bb42a36", + "tb1ps5rckm8yttuvfh4xxfy2t7hzs0vwmn98tuvx89djxacxcja59gmqshsqcn" + ] +] diff --git a/packages/wasm-utxo/test/fixtures/address/zcash.json b/packages/wasm-utxo/test/fixtures/address/zcash.json new file mode 100644 index 0000000..fa893f6 --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/zcash.json @@ -0,0 +1,42 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "t1LcxLfh8seLbbhwEwLYwMFoY3n944fm3Zu" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "t3L9BWgkrgFVS6wCSK3w5vwjdhMHtfHnT6r" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "t1XA7ZJrLBi9fdjYtnsjg68KACnW7hnktLw" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "t3e6V5S29KYr6JmdSKa2RmWchwcz8mDAmxW" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "t1JnbQN9QnGNHUzSzZFdgKyBVDNwCm1mkNU" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "t3K4Mhi7J6tuCa21avXEYBYxBP7Cr2ff4eA" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "t1S8DLCCrGxxGbkzkyorn8pLyNkiMwfxtUv" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "t3YSBvxg3jBA1gDMWGYStjR749DLxxZRPJ6" + ] +] \ No newline at end of file diff --git a/packages/wasm-utxo/test/fixtures/address/zcashTest.json b/packages/wasm-utxo/test/fixtures/address/zcashTest.json new file mode 100644 index 0000000..1779c2a --- /dev/null +++ b/packages/wasm-utxo/test/fixtures/address/zcashTest.json @@ -0,0 +1,42 @@ +[ + [ + "p2pkh", + "76a9141e231c7f9b3415daaa53ee5a7e12e120f00ec21288ac", + "tmCThfWBYGJr6jx8gc4rgCvUHemDsZkDmH6" + ], + [ + "p2sh", + "a91411510d2560794b3ed7bf734bc0e030e70e4db42d87", + "t288NZMrzYi6oednBEnw8UZvGoqX4Z6NXys" + ], + [ + "p2pkh", + "76a91491b8f56f155030f74259be43dff4d94a6258d84a88ac", + "tmNzrt9LjaNfAmykLTc3QwnyuomawCeExgq" + ], + [ + "p2sh", + "a914d640bad0fafe2eeac9fc0e0f09fb899066263ebf87", + "t2S5g878HC1TTrUDBFK2UK8oM47DJgd6UzR" + ], + [ + "p2pkh", + "76a9140a058aec7588fca80070436b020c352c2891b68088ac", + "tmAdLjCdpAvsndEeSDywRBdrEpN22GoTqBR" + ], + [ + "p2sh", + "a914056f5a1c07fe38d27e554b88bd857f64bda8eb6f87", + "t273YkPDRyMWa7ibKrGEajB8pVbS1s6yunS" + ], + [ + "p2pkh", + "76a9145a8451539186feb4578b4f5613df6991e307823088ac", + "tmHxxf2hFfdTmk1CCeYAWzV1iyjoBTmqye6" + ], + [ + "p2sh", + "a9149829fb41e3c2bcf6a164310a7acbf0adcc0c3ee187", + "t2LRNydnBbdmPDuwFCHSwH3HhFha8k6ByHG" + ] +] \ No newline at end of file