Skip to content

Commit 52ad8dc

Browse files
OttoAllmendingerllm-git
andcommitted
feat(wasm-utxo): add network-specific PSBT handling
Implement BitGoPsbt to support decoding PSBTs across different Bitcoin-like networks including those with non-standard transaction formats. Add special handling for Zcash's overwintered transaction format and validate network- specific sighash types. Issue: BTC-2652 Co-authored-by: llm-git <[email protected]>
1 parent 0d7ed37 commit 52ad8dc

File tree

57 files changed

+5757
-252
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+5757
-252
lines changed

packages/wasm-utxo/src/bitgo_psbt/mod.rs

Lines changed: 582 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
//! BitGo transaction utilities for handling network-specific transaction variants
2+
//!
3+
//! This module provides utilities for working with transactions across different
4+
//! bitcoin-like networks, including those with non-standard sighash types and
5+
//! transaction formats.
6+
7+
use crate::networks::Network;
8+
9+
/// Bitcoin Cash and related forks use SIGHASH_FORKID flag
10+
const SIGHASH_FORKID: u32 = 0x40;
11+
12+
/// Standard sighash type values
13+
const SIGHASH_ALL: u32 = 0x01;
14+
const SIGHASH_NONE: u32 = 0x02;
15+
const SIGHASH_SINGLE: u32 = 0x03;
16+
const SIGHASH_ANYONECANPAY: u32 = 0x80;
17+
18+
/// Validates a sighash type for a given network
19+
///
20+
/// Different networks have different valid sighash types:
21+
/// - Bitcoin and most networks: 0, 1, 2, 3, and combinations with ANYONECANPAY (0x80)
22+
/// - Bitcoin Cash/BSV/Ecash: Same as above, but also with SIGHASH_FORKID (0x40)
23+
///
24+
/// # Arguments
25+
///
26+
/// * `sighash_type` - The sighash type value to validate
27+
/// * `network` - The network context for validation
28+
///
29+
/// # Returns
30+
///
31+
/// `Ok(())` if the sighash type is valid for the network, otherwise `Err` with a description
32+
pub fn validate_sighash_type(sighash_type: u32, network: Network) -> Result<(), String> {
33+
// Handle the special case of 0 (no sighash type specified)
34+
if sighash_type == 0 {
35+
return Ok(());
36+
}
37+
38+
// Determine if this network uses SIGHASH_FORKID
39+
// Bitcoin Cash, Bitcoin Gold, Bitcoin SV, and Ecash all use SIGHASH_FORKID
40+
let uses_forkid = matches!(
41+
network.mainnet(),
42+
Network::BitcoinCash | Network::BitcoinGold | Network::BitcoinSV | Network::Ecash
43+
);
44+
45+
// Extract the base sighash type (without flags)
46+
let has_forkid = (sighash_type & SIGHASH_FORKID) != 0;
47+
let has_anyonecanpay = (sighash_type & SIGHASH_ANYONECANPAY) != 0;
48+
let base_type = sighash_type & 0x1F; // Mask off flags to get base type
49+
50+
// Validate FORKID usage
51+
if has_forkid && !uses_forkid {
52+
return Err(format!(
53+
"SIGHASH_FORKID (0x40) is not valid for network {:?}. Sighash type: 0x{:02x}",
54+
network, sighash_type
55+
));
56+
}
57+
58+
// For Bitcoin Cash and forks, FORKID is required
59+
if uses_forkid && !has_forkid {
60+
return Err(format!(
61+
"SIGHASH_FORKID (0x40) is required for network {:?}. Sighash type: 0x{:02x}",
62+
network, sighash_type
63+
));
64+
}
65+
66+
// Validate the base sighash type
67+
match base_type {
68+
SIGHASH_ALL | SIGHASH_NONE | SIGHASH_SINGLE => Ok(()),
69+
_ => Err(format!(
70+
"Invalid base sighash type: 0x{:02x}. Expected SIGHASH_ALL (0x01), SIGHASH_NONE (0x02), or SIGHASH_SINGLE (0x03). Full sighash type: 0x{:02x}{}{}",
71+
base_type,
72+
sighash_type,
73+
if has_anyonecanpay { " (with ANYONECANPAY)" } else { "" },
74+
if has_forkid { " (with FORKID)" } else { "" }
75+
)),
76+
}
77+
}
78+
79+
#[cfg(test)]
80+
mod tests {
81+
use super::*;
82+
83+
#[test]
84+
fn test_bitcoin_sighash_types() {
85+
// Bitcoin accepts standard types without FORKID
86+
assert!(validate_sighash_type(0, Network::Bitcoin).is_ok());
87+
assert!(validate_sighash_type(SIGHASH_ALL, Network::Bitcoin).is_ok());
88+
assert!(validate_sighash_type(SIGHASH_NONE, Network::Bitcoin).is_ok());
89+
assert!(validate_sighash_type(SIGHASH_SINGLE, Network::Bitcoin).is_ok());
90+
assert!(
91+
validate_sighash_type(SIGHASH_ALL | SIGHASH_ANYONECANPAY, Network::Bitcoin).is_ok()
92+
);
93+
94+
// Bitcoin does not accept FORKID
95+
assert!(validate_sighash_type(SIGHASH_ALL | SIGHASH_FORKID, Network::Bitcoin).is_err());
96+
assert!(validate_sighash_type(0x41, Network::Bitcoin).is_err());
97+
}
98+
99+
#[test]
100+
fn test_bitcoin_cash_sighash_types() {
101+
// Bitcoin Cash requires FORKID
102+
assert!(validate_sighash_type(0, Network::BitcoinCash).is_ok()); // Special case: 0 is allowed
103+
assert!(validate_sighash_type(0x41, Network::BitcoinCash).is_ok()); // ALL | FORKID
104+
assert!(validate_sighash_type(0x42, Network::BitcoinCash).is_ok()); // NONE | FORKID
105+
assert!(validate_sighash_type(0x43, Network::BitcoinCash).is_ok()); // SINGLE | FORKID
106+
assert!(validate_sighash_type(0xC1, Network::BitcoinCash).is_ok()); // ALL | FORKID | ANYONECANPAY
107+
108+
// Bitcoin Cash does not accept types without FORKID (except 0)
109+
assert!(validate_sighash_type(SIGHASH_ALL, Network::BitcoinCash).is_err());
110+
assert!(validate_sighash_type(SIGHASH_NONE, Network::BitcoinCash).is_err());
111+
assert!(validate_sighash_type(SIGHASH_SINGLE, Network::BitcoinCash).is_err());
112+
}
113+
114+
#[test]
115+
fn test_ecash_sighash_types() {
116+
// Ecash also uses FORKID (Bitcoin Cash fork)
117+
assert!(validate_sighash_type(0, Network::Ecash).is_ok());
118+
assert!(validate_sighash_type(0x41, Network::Ecash).is_ok()); // ALL | FORKID
119+
assert!(validate_sighash_type(SIGHASH_ALL, Network::Ecash).is_err()); // Missing FORKID
120+
}
121+
122+
#[test]
123+
fn test_bitcoin_gold_sighash_types() {
124+
// Bitcoin Gold uses FORKID (Bitcoin Cash fork)
125+
assert!(validate_sighash_type(0, Network::BitcoinGold).is_ok());
126+
assert!(validate_sighash_type(0x41, Network::BitcoinGold).is_ok()); // ALL | FORKID
127+
assert!(validate_sighash_type(SIGHASH_ALL, Network::BitcoinGold).is_err());
128+
// Missing FORKID
129+
}
130+
131+
#[test]
132+
fn test_bitcoin_sv_sighash_types() {
133+
// Bitcoin SV also uses FORKID (Bitcoin Cash fork)
134+
assert!(validate_sighash_type(0, Network::BitcoinSV).is_ok());
135+
assert!(validate_sighash_type(0x41, Network::BitcoinSV).is_ok()); // ALL | FORKID
136+
assert!(validate_sighash_type(SIGHASH_ALL, Network::BitcoinSV).is_err());
137+
// Missing FORKID
138+
}
139+
140+
#[test]
141+
fn test_invalid_base_types() {
142+
// Invalid base type
143+
assert!(validate_sighash_type(0x04, Network::Bitcoin).is_err());
144+
assert!(validate_sighash_type(0x44, Network::BitcoinCash).is_err()); // Invalid base with FORKID
145+
}
146+
147+
#[test]
148+
fn test_litecoin_sighash_types() {
149+
// Litecoin uses standard Bitcoin sighash types
150+
assert!(validate_sighash_type(0, Network::Litecoin).is_ok());
151+
assert!(validate_sighash_type(SIGHASH_ALL, Network::Litecoin).is_ok());
152+
assert!(validate_sighash_type(SIGHASH_ALL | SIGHASH_FORKID, Network::Litecoin).is_err());
153+
}
154+
155+
#[test]
156+
fn test_dogecoin_sighash_types() {
157+
// Dogecoin uses standard Bitcoin sighash types
158+
assert!(validate_sighash_type(0, Network::Dogecoin).is_ok());
159+
assert!(validate_sighash_type(SIGHASH_ALL, Network::Dogecoin).is_ok());
160+
assert!(validate_sighash_type(SIGHASH_ALL | SIGHASH_FORKID, Network::Dogecoin).is_err());
161+
}
162+
163+
#[test]
164+
fn test_sighash_anyonecanpay_combinations() {
165+
// Test ANYONECANPAY flag combinations for Bitcoin
166+
assert!(
167+
validate_sighash_type(SIGHASH_ALL | SIGHASH_ANYONECANPAY, Network::Bitcoin).is_ok()
168+
);
169+
assert!(
170+
validate_sighash_type(SIGHASH_NONE | SIGHASH_ANYONECANPAY, Network::Bitcoin).is_ok()
171+
);
172+
assert!(
173+
validate_sighash_type(SIGHASH_SINGLE | SIGHASH_ANYONECANPAY, Network::Bitcoin).is_ok()
174+
);
175+
176+
// Test ANYONECANPAY flag combinations for Bitcoin Cash (must include FORKID)
177+
assert!(validate_sighash_type(
178+
SIGHASH_ALL | SIGHASH_FORKID | SIGHASH_ANYONECANPAY,
179+
Network::BitcoinCash
180+
)
181+
.is_ok());
182+
assert!(validate_sighash_type(
183+
SIGHASH_NONE | SIGHASH_FORKID | SIGHASH_ANYONECANPAY,
184+
Network::BitcoinCash
185+
)
186+
.is_ok());
187+
assert!(validate_sighash_type(
188+
SIGHASH_SINGLE | SIGHASH_FORKID | SIGHASH_ANYONECANPAY,
189+
Network::BitcoinCash
190+
)
191+
.is_ok());
192+
193+
// Without FORKID should fail for Bitcoin Cash
194+
assert!(
195+
validate_sighash_type(SIGHASH_ALL | SIGHASH_ANYONECANPAY, Network::BitcoinCash)
196+
.is_err()
197+
);
198+
}
199+
}

0 commit comments

Comments
 (0)