mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-08-12 02:03:09 -07:00
Merge commit 'refs/pull/38/head' of github.com:bitcoinresearchkit/brk into next
# Conflicts: # crates/brk_types/src/output_type.rs # packages/brk_client/brk_client/__init__.py
This commit is contained in:
@@ -825,12 +825,13 @@ function _validateHashPrefixNibbles(nibbles) {
|
||||
function _addressPayloadLengths(addrType) {
|
||||
switch (addrType) {
|
||||
case "p2a": return [2];
|
||||
case "p2pk": return [33, 65];
|
||||
case "p2pk33": return [33];
|
||||
case "p2pk65": return [65];
|
||||
case "p2pkh":
|
||||
case "p2sh":
|
||||
case "v0_p2wpkh": return [20];
|
||||
case "v0_p2wsh":
|
||||
case "v1_p2tr": return [32];
|
||||
case "p2wpkh": return [20];
|
||||
case "p2wsh":
|
||||
case "p2tr": return [32];
|
||||
default:
|
||||
throw new Error(`Unsupported address type for address payload hash-prefix: ${addrType}`);
|
||||
}
|
||||
|
||||
@@ -242,11 +242,13 @@ def _validate_hash_prefix_nibbles(nibbles: int) -> None:
|
||||
def _address_payload_lengths(addr_type: OutputType) -> Tuple[int, ...]:
|
||||
if addr_type == "p2a":
|
||||
return (2,)
|
||||
if addr_type == "p2pk":
|
||||
return (33, 65)
|
||||
if addr_type in ("p2pkh", "p2sh", "v0_p2wpkh"):
|
||||
if addr_type == "p2pk33":
|
||||
return (33,)
|
||||
if addr_type == "p2pk65":
|
||||
return (65,)
|
||||
if addr_type in ("p2pkh", "p2sh", "p2wpkh"):
|
||||
return (20,)
|
||||
if addr_type in ("v0_p2wsh", "v1_p2tr"):
|
||||
if addr_type in ("p2wsh", "p2tr"):
|
||||
return (32,)
|
||||
raise ValueError(f"Unsupported address type for address payload hash-prefix: {addr_type}")
|
||||
|
||||
|
||||
@@ -148,23 +148,6 @@ fn generate_get_method(output: &mut String, endpoint: &Endpoint) {
|
||||
"get_text"
|
||||
};
|
||||
|
||||
if endpoint.path == "/api/address/hash-prefix/{addr_type}/{prefix}" {
|
||||
writeln!(
|
||||
output,
|
||||
" let addr_type = address_payload_type_path(addr_type)?;"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" self.base.{}(&format!(\"{}\"{}))",
|
||||
fetch_method, path, index_arg
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(output, " }}").unwrap();
|
||||
writeln!(output).unwrap();
|
||||
return;
|
||||
}
|
||||
|
||||
if endpoint.query_params.is_empty() {
|
||||
writeln!(
|
||||
output,
|
||||
|
||||
@@ -83,7 +83,6 @@ fn validate_address_payload_for_type(addr_type: OutputType, payload: &[u8]) -> R
|
||||
return Err(BrkError {{ message: format!("Unsupported address type for address payload hash-prefix: {{addr_type:?}}") }});
|
||||
}},
|
||||
}};
|
||||
let addr_type = address_payload_type_path(addr_type)?;
|
||||
|
||||
if !expected.contains(&payload.len()) {{
|
||||
let joined = expected
|
||||
@@ -97,18 +96,16 @@ fn validate_address_payload_for_type(addr_type: OutputType, payload: &[u8]) -> R
|
||||
Ok(())
|
||||
}}
|
||||
|
||||
fn address_payload_type_path(addr_type: OutputType) -> Result<&'static str> {{
|
||||
match addr_type {{
|
||||
OutputType::P2A => Ok("p2a"),
|
||||
OutputType::P2PK33 | OutputType::P2PK65 => Ok("p2pk"),
|
||||
OutputType::P2PKH => Ok("p2pkh"),
|
||||
OutputType::P2SH => Ok("p2sh"),
|
||||
OutputType::P2WPKH => Ok("v0_p2wpkh"),
|
||||
OutputType::P2WSH => Ok("v0_p2wsh"),
|
||||
OutputType::P2TR => Ok("v1_p2tr"),
|
||||
OutputType::P2MS | OutputType::OpReturn | OutputType::Empty | OutputType::Unknown => {{
|
||||
Err(BrkError {{ message: format!("Unsupported address type for address payload hash-prefix: {{addr_type:?}}") }})
|
||||
}},
|
||||
#[cfg(test)]
|
||||
mod address_payload_tests {{
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn p2pk_payload_lengths_are_distinct() {{
|
||||
assert!(validate_address_payload_for_type(OutputType::P2PK33, &[0; 33]).is_ok());
|
||||
assert!(validate_address_payload_for_type(OutputType::P2PK65, &[0; 65]).is_ok());
|
||||
assert!(validate_address_payload_for_type(OutputType::P2PK33, &[0; 65]).is_err());
|
||||
assert!(validate_address_payload_for_type(OutputType::P2PK65, &[0; 33]).is_err());
|
||||
}}
|
||||
}}
|
||||
|
||||
|
||||
@@ -72,7 +72,6 @@ fn validate_address_payload_for_type(addr_type: OutputType, payload: &[u8]) -> R
|
||||
return Err(BrkError { message: format!("Unsupported address type for address payload hash-prefix: {addr_type:?}") });
|
||||
},
|
||||
};
|
||||
let addr_type = address_payload_type_path(addr_type)?;
|
||||
|
||||
if !expected.contains(&payload.len()) {
|
||||
let joined = expected
|
||||
@@ -86,18 +85,16 @@ fn validate_address_payload_for_type(addr_type: OutputType, payload: &[u8]) -> R
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn address_payload_type_path(addr_type: OutputType) -> Result<&'static str> {
|
||||
match addr_type {
|
||||
OutputType::P2A => Ok("p2a"),
|
||||
OutputType::P2PK33 | OutputType::P2PK65 => Ok("p2pk"),
|
||||
OutputType::P2PKH => Ok("p2pkh"),
|
||||
OutputType::P2SH => Ok("p2sh"),
|
||||
OutputType::P2WPKH => Ok("v0_p2wpkh"),
|
||||
OutputType::P2WSH => Ok("v0_p2wsh"),
|
||||
OutputType::P2TR => Ok("v1_p2tr"),
|
||||
OutputType::P2MS | OutputType::OpReturn | OutputType::Empty | OutputType::Unknown => {
|
||||
Err(BrkError { message: format!("Unsupported address type for address payload hash-prefix: {addr_type:?}") })
|
||||
},
|
||||
#[cfg(test)]
|
||||
mod address_payload_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn p2pk_payload_lengths_are_distinct() {
|
||||
assert!(validate_address_payload_for_type(OutputType::P2PK33, &[0; 33]).is_ok());
|
||||
assert!(validate_address_payload_for_type(OutputType::P2PK65, &[0; 65]).is_ok());
|
||||
assert!(validate_address_payload_for_type(OutputType::P2PK33, &[0; 65]).is_err());
|
||||
assert!(validate_address_payload_for_type(OutputType::P2PK65, &[0; 33]).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10150,7 +10147,6 @@ impl BrkClient {
|
||||
///
|
||||
/// Endpoint: `GET /api/address/hash-prefix/{addr_type}/{prefix}`
|
||||
pub fn get_address_hash_prefix_matches(&self, addr_type: OutputType, prefix: &str) -> Result<AddrHashPrefixMatches> {
|
||||
let addr_type = address_payload_type_path(addr_type)?;
|
||||
self.base.get_json(&format!("/api/address/hash-prefix/{addr_type}/{prefix}"))
|
||||
}
|
||||
|
||||
|
||||
@@ -42,3 +42,20 @@ fn address_hash_prefix_uses_brk_address_parser() {
|
||||
address_payload_hash_prefix(&decoded.payload, 8).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn p2pk_payload_types_are_distinct() {
|
||||
let p2pk33 = decode_address_payload(
|
||||
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(p2pk33.addr_type, OutputType::P2PK33);
|
||||
assert_eq!(p2pk33.payload.len(), 33);
|
||||
|
||||
let p2pk65 = decode_address_payload(
|
||||
"04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(p2pk65.addr_type, OutputType::P2PK65);
|
||||
assert_eq!(p2pk65.payload.len(), 65);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ pub struct AddrStats {
|
||||
#[serde(rename = "address")]
|
||||
pub addr: Addr,
|
||||
|
||||
/// Address type (p2pkh, p2sh, v0_p2wpkh, v0_p2wsh, v1_p2tr, etc.)
|
||||
/// BRK address type (p2pk33, p2pk65, p2pkh, p2sh, p2wpkh, p2wsh, p2tr, etc.)
|
||||
pub addr_type: OutputType,
|
||||
|
||||
/// Statistics for confirmed transactions on the blockchain
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
#![allow(
|
||||
unreachable_patterns,
|
||||
reason = "P2PK65 and P2PK33 both serialize as 'p2pk'"
|
||||
)]
|
||||
|
||||
use bitcoin::{
|
||||
AddressType, ScriptBuf,
|
||||
opcodes::all::{
|
||||
@@ -38,13 +33,27 @@ use crate::AddrBytes;
|
||||
#[repr(u8)]
|
||||
/// Type (P2PKH, P2WPKH, P2SH, P2TR, etc.)
|
||||
pub enum OutputType {
|
||||
#[serde(rename = "p2pk")]
|
||||
#[strum(serialize = "p2pk")]
|
||||
P2PK65,
|
||||
#[serde(rename = "p2pk")]
|
||||
#[strum(serialize = "p2pk")]
|
||||
P2PK33,
|
||||
P2PKH,
|
||||
P2MS,
|
||||
P2SH,
|
||||
OpReturn,
|
||||
P2WPKH,
|
||||
P2WSH,
|
||||
P2TR,
|
||||
P2A,
|
||||
Empty,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Hash)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
/// Output type names used by Esplora and mempool.space.
|
||||
pub enum OutputTypeNormalized {
|
||||
P2PK,
|
||||
P2PKH,
|
||||
#[serde(rename = "multisig")]
|
||||
#[strum(serialize = "multisig")]
|
||||
P2MS,
|
||||
@@ -124,6 +133,22 @@ impl OutputType {
|
||||
!self.is_spendable()
|
||||
}
|
||||
|
||||
pub const fn normalized(self) -> OutputTypeNormalized {
|
||||
match self {
|
||||
Self::P2PK65 | Self::P2PK33 => OutputTypeNormalized::P2PK,
|
||||
Self::P2PKH => OutputTypeNormalized::P2PKH,
|
||||
Self::P2MS => OutputTypeNormalized::P2MS,
|
||||
Self::P2SH => OutputTypeNormalized::P2SH,
|
||||
Self::OpReturn => OutputTypeNormalized::OpReturn,
|
||||
Self::P2WPKH => OutputTypeNormalized::P2WPKH,
|
||||
Self::P2WSH => OutputTypeNormalized::P2WSH,
|
||||
Self::P2TR => OutputTypeNormalized::P2TR,
|
||||
Self::P2A => OutputTypeNormalized::P2A,
|
||||
Self::Empty => OutputTypeNormalized::Empty,
|
||||
Self::Unknown => OutputTypeNormalized::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the address type's public key is revealed at funding time
|
||||
/// (vs. only at spending time). For P2PK33/P2PK65 the pubkey is directly
|
||||
/// in the locking script; for P2TR the tweaked output key is in the
|
||||
@@ -287,3 +312,101 @@ impl Pco for OutputType {
|
||||
}
|
||||
|
||||
impl TransparentPco<u8> for OutputType {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn native_and_normalized_names_are_distinct() {
|
||||
let cases = [
|
||||
(
|
||||
OutputType::P2PK65,
|
||||
"p2pk65",
|
||||
OutputTypeNormalized::P2PK,
|
||||
"p2pk",
|
||||
),
|
||||
(
|
||||
OutputType::P2PK33,
|
||||
"p2pk33",
|
||||
OutputTypeNormalized::P2PK,
|
||||
"p2pk",
|
||||
),
|
||||
(
|
||||
OutputType::P2PKH,
|
||||
"p2pkh",
|
||||
OutputTypeNormalized::P2PKH,
|
||||
"p2pkh",
|
||||
),
|
||||
(
|
||||
OutputType::P2MS,
|
||||
"p2ms",
|
||||
OutputTypeNormalized::P2MS,
|
||||
"multisig",
|
||||
),
|
||||
(OutputType::P2SH, "p2sh", OutputTypeNormalized::P2SH, "p2sh"),
|
||||
(
|
||||
OutputType::OpReturn,
|
||||
"opreturn",
|
||||
OutputTypeNormalized::OpReturn,
|
||||
"op_return",
|
||||
),
|
||||
(
|
||||
OutputType::P2WPKH,
|
||||
"p2wpkh",
|
||||
OutputTypeNormalized::P2WPKH,
|
||||
"v0_p2wpkh",
|
||||
),
|
||||
(
|
||||
OutputType::P2WSH,
|
||||
"p2wsh",
|
||||
OutputTypeNormalized::P2WSH,
|
||||
"v0_p2wsh",
|
||||
),
|
||||
(
|
||||
OutputType::P2TR,
|
||||
"p2tr",
|
||||
OutputTypeNormalized::P2TR,
|
||||
"v1_p2tr",
|
||||
),
|
||||
(OutputType::P2A, "p2a", OutputTypeNormalized::P2A, "p2a"),
|
||||
(
|
||||
OutputType::Empty,
|
||||
"empty",
|
||||
OutputTypeNormalized::Empty,
|
||||
"empty",
|
||||
),
|
||||
(
|
||||
OutputType::Unknown,
|
||||
"unknown",
|
||||
OutputTypeNormalized::Unknown,
|
||||
"unknown",
|
||||
),
|
||||
];
|
||||
|
||||
for (native, native_name, normalized, normalized_name) in cases {
|
||||
assert_eq!(native.to_string(), native_name);
|
||||
assert_eq!(native.normalized(), normalized);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&native).unwrap(),
|
||||
format!(r#""{native_name}""#)
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<OutputType>(&format!(r#""{native_name}""#)).unwrap(),
|
||||
native
|
||||
);
|
||||
assert_eq!(normalized.to_string(), normalized_name);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&normalized).unwrap(),
|
||||
format!(r#""{normalized_name}""#)
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<OutputTypeNormalized>(&format!(r#""{normalized_name}""#))
|
||||
.unwrap(),
|
||||
normalized
|
||||
);
|
||||
}
|
||||
|
||||
assert!(serde_json::from_str::<OutputType>(r#""p2pk""#).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,10 +26,10 @@ pub struct TxOut {
|
||||
)]
|
||||
script_pubkey_asm: (),
|
||||
|
||||
/// Script type (p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr, op_return, etc.)
|
||||
/// Esplora/mempool.space script type
|
||||
#[allow(dead_code)]
|
||||
#[serde(skip, rename = "scriptpubkey_type")]
|
||||
#[schemars(with = "OutputType", example = &"v0_p2wpkh")]
|
||||
#[schemars(with = "crate::OutputTypeNormalized", example = &"v0_p2wpkh")]
|
||||
script_pubkey_type: (),
|
||||
|
||||
/// Bitcoin address (if applicable, None for OP_RETURN)
|
||||
@@ -108,7 +108,7 @@ impl Serialize for TxOut {
|
||||
let mut state = serializer.serialize_struct("TxOut", field_count)?;
|
||||
state.serialize_field("scriptpubkey", &self.script_pubkey.to_hex_string())?;
|
||||
state.serialize_field("scriptpubkey_asm", &self.script_pubkey_asm())?;
|
||||
state.serialize_field("scriptpubkey_type", &output_type)?;
|
||||
state.serialize_field("scriptpubkey_type", &output_type.normalized())?;
|
||||
if let Some(addr) = &addr {
|
||||
state.serialize_field("scriptpubkey_address", addr)?;
|
||||
}
|
||||
@@ -116,3 +116,59 @@ impl Serialize for TxOut {
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn p2pk_script(pubkey: &[u8]) -> ScriptBuf {
|
||||
let mut script = Vec::with_capacity(pubkey.len() + 2);
|
||||
script.push(pubkey.len() as u8);
|
||||
script.extend_from_slice(pubkey);
|
||||
script.push(0xac);
|
||||
ScriptBuf::from_bytes(script)
|
||||
}
|
||||
|
||||
fn script_type(script: ScriptBuf) -> (OutputType, String) {
|
||||
let txout = TxOut::from((script, Sats::new(0)));
|
||||
let output_type = txout.type_();
|
||||
let value = serde_json::to_value(txout).unwrap();
|
||||
let script_type = value["scriptpubkey_type"].as_str().unwrap().to_owned();
|
||||
(output_type, script_type)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_type_uses_normalized_names() {
|
||||
let p2pk33 = [
|
||||
0x02, 0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce,
|
||||
0x87, 0x0b, 0x07, 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81,
|
||||
0x5b, 0x16, 0xf8, 0x17, 0x98,
|
||||
];
|
||||
let p2pk65 = [
|
||||
0x04, 0x67, 0x8a, 0xfd, 0xb0, 0xfe, 0x55, 0x48, 0x27, 0x19, 0x67, 0xf1, 0xa6, 0x71,
|
||||
0x30, 0xb7, 0x10, 0x5c, 0xd6, 0xa8, 0x28, 0xe0, 0x39, 0x09, 0xa6, 0x79, 0x62, 0xe0,
|
||||
0xea, 0x1f, 0x61, 0xde, 0xb6, 0x49, 0xf6, 0xbc, 0x3f, 0x4c, 0xef, 0x38, 0xc4, 0xf3,
|
||||
0x55, 0x04, 0xe5, 0x1e, 0xc1, 0x12, 0xde, 0x5c, 0x38, 0x4d, 0xf7, 0xba, 0x0b, 0x8d,
|
||||
0x57, 0x8a, 0x4c, 0x70, 0x2b, 0x6b, 0xf1, 0x1d, 0x5f,
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
script_type(p2pk_script(&p2pk33)),
|
||||
(OutputType::P2PK33, "p2pk".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
script_type(p2pk_script(&p2pk65)),
|
||||
(OutputType::P2PK65, "p2pk".to_string())
|
||||
);
|
||||
let mut p2wpkh = vec![0; 22];
|
||||
p2wpkh[1] = 0x14;
|
||||
assert_eq!(
|
||||
script_type(ScriptBuf::from_bytes(p2wpkh)),
|
||||
(OutputType::P2WPKH, "v0_p2wpkh".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
script_type(ScriptBuf::from_bytes(vec![0x6a])),
|
||||
(OutputType::OpReturn, "op_return".to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ Bitcoin address string
|
||||
|
||||
Defined in: [Developer/brk/modules/brk-client/index.js:70](https://github.com/bitcoinresearchkit/brk/blob/51e058e08ee90a54012facf825ef4a1944101a66/modules/brk-client/index.js#L70)
|
||||
|
||||
Address type (p2pkh, p2sh, v0_p2wpkh, v0_p2wsh, v1_p2tr, etc.)
|
||||
BRK address type (p2pk33, p2pk65, p2pkh, p2sh, p2wpkh, p2wsh, p2tr, etc.)
|
||||
|
||||
***
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Type Alias: OutputType
|
||||
|
||||
> **OutputType** = `"p2pk"` \| `"p2pk"` \| `"p2pkh"` \| `"multisig"` \| `"p2sh"` \| `"op_return"` \| `"v0_p2wpkh"` \| `"v0_p2wsh"` \| `"v1_p2tr"` \| `"p2a"` \| `"empty"` \| `"unknown"`
|
||||
> **OutputType** = `"p2pk65"` \| `"p2pk33"` \| `"p2pkh"` \| `"p2ms"` \| `"p2sh"` \| `"opreturn"` \| `"p2wpkh"` \| `"p2wsh"` \| `"p2tr"` \| `"p2a"` \| `"empty"` \| `"unknown"`
|
||||
|
||||
Defined in: [Developer/brk/modules/brk-client/index.js:822](https://github.com/bitcoinresearchkit/brk/blob/51e058e08ee90a54012facf825ef4a1944101a66/modules/brk-client/index.js#L822)
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ address payload bytes.
|
||||
*
|
||||
* @typedef {Object} AddrStats
|
||||
* @property {Addr} address - Bitcoin address string
|
||||
* @property {OutputType} addrType - Address type (p2pkh, p2sh, v0_p2wpkh, v0_p2wsh, v1_p2tr, etc.)
|
||||
* @property {OutputType} addrType - BRK address type (p2pk33, p2pk65, p2pkh, p2sh, p2wpkh, p2wsh, p2tr, etc.)
|
||||
* @property {AddrChainStats} chainStats - Statistics for confirmed transactions on the blockchain
|
||||
* @property {AddrMempoolStats} mempoolStats - Statistics for unconfirmed transactions in the mempool
|
||||
* @property {Sats} balance - Total current balance in satoshis, including pending (unconfirmed) mempool changes
|
||||
@@ -819,7 +819,7 @@ ancestors and no descendants (matches mempool.space).
|
||||
/**
|
||||
* Type (P2PKH, P2WPKH, P2SH, P2TR, etc.)
|
||||
*
|
||||
* @typedef {("p2pk"|"p2pk"|"p2pkh"|"multisig"|"p2sh"|"op_return"|"v0_p2wpkh"|"v0_p2wsh"|"v1_p2tr"|"p2a"|"empty"|"unknown")} OutputType
|
||||
* @typedef {("p2pk65"|"p2pk33"|"p2pkh"|"p2ms"|"p2sh"|"opreturn"|"p2wpkh"|"p2wsh"|"p2tr"|"p2a"|"empty"|"unknown")} OutputType
|
||||
*/
|
||||
/** @typedef {TypeIndex} P2AAddrIndex */
|
||||
/** @typedef {U8x2} P2ABytes */
|
||||
@@ -2298,12 +2298,13 @@ function _validateHashPrefixNibbles(nibbles) {
|
||||
function _addressPayloadLengths(addrType) {
|
||||
switch (addrType) {
|
||||
case "p2a": return [2];
|
||||
case "p2pk": return [33, 65];
|
||||
case "p2pk33": return [33];
|
||||
case "p2pk65": return [65];
|
||||
case "p2pkh":
|
||||
case "p2sh":
|
||||
case "v0_p2wpkh": return [20];
|
||||
case "v0_p2wsh":
|
||||
case "v1_p2tr": return [32];
|
||||
case "p2wpkh": return [20];
|
||||
case "p2wsh":
|
||||
case "p2tr": return [32];
|
||||
default:
|
||||
throw new Error(`Unsupported address type for address payload hash-prefix: ${addrType}`);
|
||||
}
|
||||
|
||||
@@ -24,11 +24,35 @@ assert.deepEqual(
|
||||
client.getAddressPayloadHashPrefixMatches("p2pkh", Uint8Array.from({ length: 20 }, (_, i) => i), 8),
|
||||
{ addrType: "p2pkh", prefix: "c3327ecb", truncated: false, addresses: [] },
|
||||
);
|
||||
for (const [addrType, length] of [["p2pk33", 33], ["p2pk65", 65]]) {
|
||||
const payload = Uint8Array.from({ length }, (_, i) => i);
|
||||
assert.deepEqual(
|
||||
client.getAddressPayloadHashPrefixMatches(addrType, payload, 8),
|
||||
{
|
||||
addrType,
|
||||
prefix: addressPayloadHashPrefix(payload, 8),
|
||||
truncated: false,
|
||||
addresses: [],
|
||||
},
|
||||
);
|
||||
}
|
||||
assert.throws(
|
||||
() => client.getAddressPayloadHashPrefixMatches("p2pkh", Uint8Array.of(1, 2), 8),
|
||||
/p2pkh address payload length 20 bytes/,
|
||||
);
|
||||
assert.throws(
|
||||
() => client.getAddressPayloadHashPrefixMatches("op_return", Uint8Array.of(1, 2), 8),
|
||||
() => client.getAddressPayloadHashPrefixMatches("p2pk33", new Uint8Array(65), 8),
|
||||
/p2pk33 address payload length 33 bytes/,
|
||||
);
|
||||
assert.throws(
|
||||
() => client.getAddressPayloadHashPrefixMatches("p2pk65", new Uint8Array(33), 8),
|
||||
/p2pk65 address payload length 65 bytes/,
|
||||
);
|
||||
assert.throws(
|
||||
() => client.getAddressPayloadHashPrefixMatches("p2pk", new Uint8Array(33), 8),
|
||||
/Unsupported address type/,
|
||||
);
|
||||
assert.throws(
|
||||
() => client.getAddressPayloadHashPrefixMatches("opreturn", Uint8Array.of(1, 2), 8),
|
||||
/Unsupported address type/,
|
||||
);
|
||||
|
||||
@@ -28,7 +28,7 @@ Sats = int
|
||||
# Index within its type (e.g., 0 for first P2WPKH address)
|
||||
TypeIndex = int
|
||||
# Type (P2PKH, P2WPKH, P2SH, P2TR, etc.)
|
||||
OutputType = Literal["p2pk", "p2pk", "p2pkh", "multisig", "p2sh", "op_return", "v0_p2wpkh", "v0_p2wsh", "v1_p2tr", "p2a", "empty", "unknown"]
|
||||
OutputType = Literal["p2pk65", "p2pk33", "p2pkh", "p2ms", "p2sh", "opreturn", "p2wpkh", "p2wsh", "p2tr", "p2a", "empty", "unknown"]
|
||||
# Signed satoshis (i64) - for values that can be negative.
|
||||
# Used for changes, deltas, profit/loss calculations, etc.
|
||||
SatsSigned = int
|
||||
@@ -357,7 +357,7 @@ class AddrStats(TypedDict):
|
||||
|
||||
Attributes:
|
||||
address: Bitcoin address string
|
||||
addr_type: Address type (p2pkh, p2sh, v0_p2wpkh, v0_p2wsh, v1_p2tr, etc.)
|
||||
addr_type: BRK address type (p2pk33, p2pk65, p2pkh, p2sh, p2wpkh, p2wsh, p2tr, etc.)
|
||||
chain_stats: Statistics for confirmed transactions on the blockchain
|
||||
mempool_stats: Statistics for unconfirmed transactions in the mempool
|
||||
balance: Total current balance in satoshis, including pending (unconfirmed) mempool changes
|
||||
@@ -2048,11 +2048,13 @@ def _validate_hash_prefix_nibbles(nibbles: int) -> None:
|
||||
def _address_payload_lengths(addr_type: OutputType) -> Tuple[int, ...]:
|
||||
if addr_type == "p2a":
|
||||
return (2,)
|
||||
if addr_type == "p2pk":
|
||||
return (33, 65)
|
||||
if addr_type in ("p2pkh", "p2sh", "v0_p2wpkh"):
|
||||
if addr_type == "p2pk33":
|
||||
return (33,)
|
||||
if addr_type == "p2pk65":
|
||||
return (65,)
|
||||
if addr_type in ("p2pkh", "p2sh", "p2wpkh"):
|
||||
return (20,)
|
||||
if addr_type in ("v0_p2wsh", "v1_p2tr"):
|
||||
if addr_type in ("p2wsh", "p2tr"):
|
||||
return (32,)
|
||||
raise ValueError(f"Unsupported address type for address payload hash-prefix: {addr_type}")
|
||||
|
||||
@@ -9463,4 +9465,3 @@ class BrkClient(BrkClientBase):
|
||||
|
||||
Endpoint: `GET /api.json`"""
|
||||
return self.get_json('/api.json')
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ from _lib import assert_same_structure, show
|
||||
|
||||
|
||||
KNOWN_ADDR_TYPES = {
|
||||
"p2pk", "p2pkh", "p2sh", "v0_p2wpkh", "v0_p2wsh", "v1_p2tr",
|
||||
"multisig", "op_return", "p2a", "empty", "unknown",
|
||||
"p2pk33", "p2pk65", "p2pkh", "p2sh", "p2wpkh", "p2wsh", "p2tr",
|
||||
"p2ms", "opreturn", "p2a", "empty", "unknown",
|
||||
}
|
||||
|
||||
# Static fixtures: stable addresses with known shapes.
|
||||
@@ -124,7 +124,7 @@ def test_address_invalid(brk):
|
||||
def test_address_pubkey_as_address(brk):
|
||||
"""Brk-only: hex-encoded pubkey is accepted as a P2PK address."""
|
||||
b = brk.get_address(SATOSHI_GENESIS_PUBKEY)
|
||||
assert b["addr_type"] == "p2pk", f"expected p2pk, got {b['addr_type']!r}"
|
||||
assert b["addr_type"] == "p2pk65", f"expected p2pk65, got {b['addr_type']!r}"
|
||||
assert b["chain_stats"]["funded_txo_count"] >= 1, (
|
||||
f"genesis pubkey must have at least one funded output, got "
|
||||
f"{b['chain_stats']['funded_txo_count']}"
|
||||
|
||||
@@ -42,7 +42,22 @@ def test_address_payload_hash_prefix_match_validation():
|
||||
"addresses": [],
|
||||
}
|
||||
|
||||
for addr_type, length in (("p2pk33", 33), ("p2pk65", 65)):
|
||||
payload = bytes(range(length))
|
||||
assert client.get_address_payload_hash_prefix_matches(addr_type, payload, 8) == {
|
||||
"addr_type": addr_type,
|
||||
"prefix": address_payload_hash_prefix(payload, 8),
|
||||
"truncated": False,
|
||||
"addresses": [],
|
||||
}
|
||||
|
||||
with pytest.raises(ValueError, match="p2pkh address payload length 20 bytes"):
|
||||
client.get_address_payload_hash_prefix_matches("p2pkh", b"\x01\x02", 8)
|
||||
with pytest.raises(ValueError, match="p2pk33 address payload length 33 bytes"):
|
||||
client.get_address_payload_hash_prefix_matches("p2pk33", bytes(65), 8)
|
||||
with pytest.raises(ValueError, match="p2pk65 address payload length 65 bytes"):
|
||||
client.get_address_payload_hash_prefix_matches("p2pk65", bytes(33), 8)
|
||||
with pytest.raises(ValueError, match="Unsupported address type"):
|
||||
client.get_address_payload_hash_prefix_matches("op_return", b"\x01\x02", 8)
|
||||
client.get_address_payload_hash_prefix_matches("p2pk", bytes(33), 8)
|
||||
with pytest.raises(ValueError, match="Unsupported address type"):
|
||||
client.get_address_payload_hash_prefix_matches("opreturn", b"\x01\x02", 8)
|
||||
|
||||
Reference in New Issue
Block a user