0.1.0
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
# file: kb_decoder_spl_elgamal_registry/Cargo.toml
|
||||
# version: 2
|
||||
|
||||
[package]
|
||||
name = "kb_decoder_spl_elgamal_registry"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
base64.workspace = true
|
||||
bs58.workspace = true
|
||||
kb_core = { path = "../kb_core" }
|
||||
kb_decoder_api = { path = "../kb_decoder_api" }
|
||||
kb_model = { path = "../kb_model" }
|
||||
kb_program_ids = { path = "../kb_program_ids" }
|
||||
kb_store_core = { path = "../kb_store_core" }
|
||||
serde_json.workspace = true
|
||||
sha2.workspace = true
|
||||
spl-elgamal-registry-interface.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,13 @@
|
||||
<!-- file: kb_decoder_spl_elgamal_registry/README.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Décodeur SPL ElGamal Registry
|
||||
|
||||
Décodeur strict du programme `regVYJW7tcT8zipN5YiBvHsvR5jXW1uLFxaHSbugABg`.
|
||||
|
||||
Il couvre les deux instructions publiées par `spl-elgamal-registry-interface 0.2.1` :
|
||||
|
||||
- `CreateRegistry` (`00 <offset:i8>`) ;
|
||||
- `UpdateRegistry` (`01 <offset:i8>`).
|
||||
|
||||
Un offset nul désigne un compte de contexte de preuve prévalidé. Un offset non nul désigne une instruction relative du programme natif ZK ElGamal Proof. Le décodeur ne prétend pas décoder la preuve elle-même et ne mélange jamais ce programme avec Token-2022.
|
||||
@@ -0,0 +1,11 @@
|
||||
// file: kb_decoder_spl_elgamal_registry/src/constants.rs
|
||||
// version: 2
|
||||
|
||||
//! Constants for the SPL ElGamal registry decoder.
|
||||
|
||||
pub(crate) const SURFACE_CODE: &str = "spl_elgamal_registry";
|
||||
pub(crate) const EVENT_VERSION: u32 = 1;
|
||||
pub(crate) const MAX_INSTRUCTION_BYTES: usize = 16;
|
||||
|
||||
/// Canonical tracing target for this crate.
|
||||
pub(crate) const TRACING_TARGET: &str = "kb_decoder_spl_elgamal_registry";
|
||||
@@ -0,0 +1,256 @@
|
||||
// file: kb_decoder_spl_elgamal_registry/src/decoder.rs
|
||||
// version: 6
|
||||
|
||||
//! Exact dispatch for the SPL ElGamal registry program.
|
||||
|
||||
const SURFACES: &[kb_decoder_api::DecoderSurface] = &[kb_decoder_api::DecoderSurface {
|
||||
program_id: kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID,
|
||||
surface_code: crate::SURFACE_CODE,
|
||||
priority: 100,
|
||||
}];
|
||||
|
||||
const PROGRAM_IDS: &[&str] = &[kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID];
|
||||
|
||||
/// Decoder for the dedicated Token-2022 ElGamal public-key registry program.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct SplElgamalRegistryDecoder;
|
||||
|
||||
impl kb_decoder_api::ProtocolDecoder for crate::SplElgamalRegistryDecoder {
|
||||
fn decoder_name(&self) -> &'static str {
|
||||
return "kb_decoder_spl_elgamal_registry";
|
||||
}
|
||||
|
||||
fn decoder_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
}
|
||||
|
||||
fn program_ids(&self) -> &'static [&'static str] {
|
||||
return PROGRAM_IDS;
|
||||
}
|
||||
|
||||
fn supports_observation(
|
||||
&self,
|
||||
observation: &kb_model::ProgramObservation,
|
||||
) -> kb_decoder_api::DecoderSupport {
|
||||
return if kb_decoder_api::ProtocolDecoder::handles_program_id(self, &observation.program_id)
|
||||
{
|
||||
kb_decoder_api::DecoderSupport::Yes
|
||||
} else {
|
||||
kb_decoder_api::DecoderSupport::No
|
||||
};
|
||||
}
|
||||
|
||||
fn decode_observation(
|
||||
&self,
|
||||
_observation: &kb_model::ProgramObservation,
|
||||
) -> kb_core::Result<std::vec::Vec<kb_model::DecodedProtocolEvent>> {
|
||||
return std::result::Result::Ok(std::vec::Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl kb_decoder_api::InstructionDecoder for crate::SplElgamalRegistryDecoder {
|
||||
fn identity(&self) -> kb_decoder_api::DecoderIdentity {
|
||||
return kb_decoder_api::DecoderIdentity {
|
||||
name: "spl_elgamal_registry".to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn surfaces(&self) -> &'static [kb_decoder_api::DecoderSurface] {
|
||||
return SURFACES;
|
||||
}
|
||||
|
||||
fn coverage(&self) -> std::vec::Vec<kb_decoder_api::DecoderCoverageDeclaration> {
|
||||
return [(0_u8, "create_registry"), (1_u8, "update_registry")]
|
||||
.into_iter()
|
||||
.map(|(tag, code)| {
|
||||
return kb_decoder_api::DecoderCoverageDeclaration {
|
||||
program_id: kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID
|
||||
.to_string(),
|
||||
surface_code: std::option::Option::Some(crate::SURFACE_CODE.to_string()),
|
||||
entry_kind: kb_decoder_api::DecoderCoverageEntryKind::Instruction,
|
||||
entry_code: code.to_string(),
|
||||
discriminator_hex: std::option::Option::Some(format!("{tag:02x}")),
|
||||
historical: false,
|
||||
};
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
fn recognize(
|
||||
&self,
|
||||
input: &kb_store_core::CoreInstructionReplayInput,
|
||||
) -> kb_decoder_api::DecoderRecognition {
|
||||
if input.program_id != kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID {
|
||||
return kb_decoder_api::DecoderRecognition::incompatible();
|
||||
}
|
||||
let entry = input
|
||||
.instruction_payload_json
|
||||
.as_ref()
|
||||
.and_then(|payload| return payload.get("dataBase64"))
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.and_then(|encoded| {
|
||||
use base64::Engine; // rust-rules: trait-import
|
||||
return base64::engine::general_purpose::STANDARD.decode(encoded.as_bytes()).ok();
|
||||
})
|
||||
.and_then(|bytes| return bytes.first().copied())
|
||||
.and_then(|tag| {
|
||||
return match tag {
|
||||
0 => std::option::Option::Some("create_registry".to_string()),
|
||||
1 => std::option::Option::Some("update_registry".to_string()),
|
||||
_ => std::option::Option::None,
|
||||
};
|
||||
});
|
||||
return kb_decoder_api::DecoderRecognition::compatible(
|
||||
entry.is_some(),
|
||||
100,
|
||||
std::option::Option::Some(crate::SURFACE_CODE.to_string()),
|
||||
entry,
|
||||
std::option::Option::None,
|
||||
);
|
||||
}
|
||||
|
||||
fn decode(
|
||||
&self,
|
||||
input: &kb_store_core::CoreInstructionReplayInput,
|
||||
) -> kb_decoder_api::DecoderExecutionResult {
|
||||
if input.program_id != kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID {
|
||||
return kb_decoder_api::DecoderExecutionResult::unsupported(std::option::Option::None);
|
||||
}
|
||||
let result = crate::decode(input);
|
||||
if matches!(
|
||||
result.status,
|
||||
kb_decoder_api::DecoderOutcomeStatus::Failed
|
||||
| kb_decoder_api::DecoderOutcomeStatus::Unsupported
|
||||
) {
|
||||
tracing::error!(
|
||||
target: crate::TRACING_TARGET,
|
||||
action = "decode_failure",
|
||||
signature = %input.signature,
|
||||
slot = input.slot,
|
||||
instruction_path = %input.instruction_path,
|
||||
program_id = %input.program_id,
|
||||
processor_name = "spl_elgamal_registry",
|
||||
processor_version = env!("CARGO_PKG_VERSION"),
|
||||
input_key = %input.replay_input_key,
|
||||
payload_hash = ?input.instruction_payload_hash,
|
||||
transaction_failed = input.transaction_failed,
|
||||
result_status = ?result.status,
|
||||
diagnostics = ?result.diagnostics,
|
||||
"SPL ElGamal registry instruction was not decoded successfully"
|
||||
);
|
||||
}
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
action = "decode",
|
||||
signature = %input.signature,
|
||||
instruction_path = %input.instruction_path,
|
||||
program_id = %input.program_id,
|
||||
transaction_failed = input.transaction_failed,
|
||||
result_status = ?result.status,
|
||||
observation_count = result.observations.len(),
|
||||
"SPL ElGamal registry instruction decode completed"
|
||||
);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use base64::Engine; // rust-rules: trait-import
|
||||
|
||||
fn input(
|
||||
program_id: &str,
|
||||
payload: &[u8],
|
||||
failed: bool,
|
||||
path: &str,
|
||||
) -> kb_store_core::CoreInstructionReplayInput {
|
||||
let result = kb_store_core::CoreInstructionReplayInput::new(
|
||||
format!("signature:{path}"),
|
||||
"signature",
|
||||
42,
|
||||
path,
|
||||
program_id,
|
||||
failed,
|
||||
if failed {
|
||||
std::option::Option::Some(serde_json::json!({"InstructionError":[0,"Custom"]}))
|
||||
} else {
|
||||
std::option::Option::None
|
||||
},
|
||||
serde_json::json!([]),
|
||||
serde_json::json!([]),
|
||||
std::option::Option::Some(serde_json::json!({
|
||||
"dataBase64": base64::engine::general_purpose::STANDARD.encode(payload)
|
||||
})),
|
||||
std::option::Option::None,
|
||||
serde_json::json!([]),
|
||||
serde_json::json!([]),
|
||||
serde_json::json!([]),
|
||||
serde_json::json!([]),
|
||||
);
|
||||
return match result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("registry replay input failed: {error}"),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exact_program_boundary_and_interface_id_are_preserved() {
|
||||
assert_eq!(
|
||||
spl_elgamal_registry_interface::id().to_string(),
|
||||
kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID
|
||||
);
|
||||
let decoder = crate::SplElgamalRegistryDecoder;
|
||||
assert_eq!(kb_decoder_api::InstructionDecoder::coverage(&decoder).len(), 2);
|
||||
let foreign = input(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID, &[0, 1], false, "0");
|
||||
assert_eq!(
|
||||
kb_decoder_api::InstructionDecoder::decode(&decoder, &foreign).status,
|
||||
kb_decoder_api::DecoderOutcomeStatus::Unsupported
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_and_update_decode_exact_offsets_and_commit_state() {
|
||||
for (tag, code, offset, failed, path) in [
|
||||
(0_u8, "create_registry", 1_i8, false, "0"),
|
||||
(1_u8, "update_registry", -1_i8, true, "1/0"),
|
||||
(1_u8, "update_registry", 0_i8, false, "2"),
|
||||
] {
|
||||
let input = input(
|
||||
kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID,
|
||||
&[tag, offset as u8],
|
||||
failed,
|
||||
path,
|
||||
);
|
||||
let result = kb_decoder_api::InstructionDecoder::decode(
|
||||
&crate::SplElgamalRegistryDecoder,
|
||||
&input,
|
||||
);
|
||||
assert_eq!(result.status, kb_decoder_api::DecoderOutcomeStatus::Decoded);
|
||||
assert_eq!(result.recognized_entry_code.as_deref(), std::option::Option::Some(code));
|
||||
assert_eq!(
|
||||
result.observations[0].payload_json["proofInstructionOffset"],
|
||||
serde_json::json!(offset)
|
||||
);
|
||||
assert_eq!(result.observations[0].observation_committed, !failed);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_unknown_and_suffixed_wire_fail_closed() {
|
||||
for payload in [std::vec::Vec::new(), vec![0], vec![2, 0], vec![0, 1, 2]] {
|
||||
let input = input(
|
||||
kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID,
|
||||
payload.as_slice(),
|
||||
false,
|
||||
"0",
|
||||
);
|
||||
let result = kb_decoder_api::InstructionDecoder::decode(
|
||||
&crate::SplElgamalRegistryDecoder,
|
||||
&input,
|
||||
);
|
||||
assert_eq!(result.status, kb_decoder_api::DecoderOutcomeStatus::Failed);
|
||||
assert!(result.observations.is_empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// file: kb_decoder_spl_elgamal_registry/src/lib.rs
|
||||
// version: 5
|
||||
|
||||
//! Decoder for the SPL Token-2022 ElGamal public-key registry program.
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod constants;
|
||||
mod decoder;
|
||||
mod registry;
|
||||
pub mod state;
|
||||
|
||||
/// Crate-root access to `EVENT_VERSION` from `constants`.
|
||||
pub(crate) use crate::constants::EVENT_VERSION;
|
||||
/// Crate-root access to `MAX_INSTRUCTION_BYTES` from `constants`.
|
||||
pub(crate) use crate::constants::MAX_INSTRUCTION_BYTES;
|
||||
/// Crate-root access to `SURFACE_CODE` from `constants`.
|
||||
pub(crate) use crate::constants::SURFACE_CODE;
|
||||
/// Canonical tracing target for this crate.
|
||||
pub(crate) use crate::constants::TRACING_TARGET;
|
||||
/// Crate-root access to `ParsedRegistryInstruction` from `registry`.
|
||||
pub(crate) use crate::registry::ParsedRegistryInstruction;
|
||||
/// Crate-root access to `decode` from `registry`.
|
||||
pub(crate) use crate::registry::decode;
|
||||
/// Crate-root access to `decode_payload` from `registry`.
|
||||
pub(crate) use crate::registry::decode_payload;
|
||||
/// Crate-root access to `parse` from `registry`.
|
||||
pub(crate) use crate::registry::parse;
|
||||
|
||||
/// Exact decoder for the SPL ElGamal registry program.
|
||||
pub use crate::decoder::SplElgamalRegistryDecoder;
|
||||
/// Exact byte length of one SPL ElGamal registry account.
|
||||
pub use crate::state::ELGAMAL_REGISTRY_ACCOUNT_LEN;
|
||||
/// Crate-root access to `ElGamalRegistryState` from `state`.
|
||||
pub use crate::state::ElGamalRegistryState;
|
||||
/// Parse one exact SPL ElGamal registry account.
|
||||
pub use crate::state::parse_elgamal_registry_state;
|
||||
@@ -0,0 +1,191 @@
|
||||
// file: kb_decoder_spl_elgamal_registry/src/registry.rs
|
||||
// version: 3
|
||||
|
||||
//! Exact two-byte SPL ElGamal registry instruction decoding.
|
||||
|
||||
use base64::Engine; // rust-rules: trait-import
|
||||
use sha2::Digest; // rust-rules: trait-import
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct ParsedRegistryInstruction {
|
||||
pub(crate) code: &'static str,
|
||||
pub(crate) tag: u8,
|
||||
pub(crate) proof_instruction_offset: i8,
|
||||
}
|
||||
|
||||
pub(crate) fn decode_payload(
|
||||
input: &kb_store_core::CoreInstructionReplayInput,
|
||||
) -> std::result::Result<std::vec::Vec<u8>, std::string::String> {
|
||||
let payload = match input.instruction_payload_json.as_ref() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(
|
||||
"ElGamal registry instruction payload is not retained".to_string(),
|
||||
);
|
||||
},
|
||||
};
|
||||
let encoded = match payload.get("dataBase64").and_then(serde_json::Value::as_str) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(
|
||||
"ElGamal registry payload does not contain dataBase64".to_string(),
|
||||
);
|
||||
},
|
||||
};
|
||||
if encoded.len() > 32 {
|
||||
return std::result::Result::Err(
|
||||
"ElGamal registry base64 payload is too large".to_string(),
|
||||
);
|
||||
}
|
||||
let bytes = match base64::engine::general_purpose::STANDARD.decode(encoded.as_bytes()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(format!(
|
||||
"ElGamal registry payload is not valid base64: {error}"
|
||||
));
|
||||
},
|
||||
};
|
||||
if bytes.len() > crate::MAX_INSTRUCTION_BYTES {
|
||||
return std::result::Result::Err(format!(
|
||||
"ElGamal registry payload exceeds {} bytes",
|
||||
crate::MAX_INSTRUCTION_BYTES
|
||||
));
|
||||
}
|
||||
return std::result::Result::Ok(bytes);
|
||||
}
|
||||
|
||||
pub(crate) fn parse(
|
||||
bytes: &[u8],
|
||||
) -> std::result::Result<crate::ParsedRegistryInstruction, std::string::String> {
|
||||
if bytes.len() != 2 {
|
||||
return std::result::Result::Err(format!(
|
||||
"ElGamal registry instruction requires exactly 2 bytes; received {}",
|
||||
bytes.len()
|
||||
));
|
||||
}
|
||||
let code = match bytes[0] {
|
||||
0 => "create_registry",
|
||||
1 => "update_registry",
|
||||
tag => return std::result::Result::Err(format!("unknown ElGamal registry tag {tag}")),
|
||||
};
|
||||
return std::result::Result::Ok(crate::ParsedRegistryInstruction {
|
||||
code,
|
||||
tag: bytes[0],
|
||||
proof_instruction_offset: bytes[1] as i8,
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn decode(
|
||||
input: &kb_store_core::CoreInstructionReplayInput,
|
||||
) -> kb_decoder_api::DecoderExecutionResult {
|
||||
let bytes = match crate::decode_payload(input) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(message) => {
|
||||
return kb_decoder_api::DecoderExecutionResult {
|
||||
status: kb_decoder_api::DecoderOutcomeStatus::Failed,
|
||||
recognized_entry_code: std::option::Option::None,
|
||||
observations: std::vec::Vec::new(),
|
||||
diagnostics: std::vec![kb_decoder_api::DecoderDiagnostic {
|
||||
code: "elgamal_registry_payload_invalid".to_string(),
|
||||
message,
|
||||
retriable: false,
|
||||
}],
|
||||
};
|
||||
},
|
||||
};
|
||||
let parsed = match crate::parse(bytes.as_slice()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(message) => {
|
||||
return kb_decoder_api::DecoderExecutionResult {
|
||||
status: kb_decoder_api::DecoderOutcomeStatus::Failed,
|
||||
recognized_entry_code: bytes.first().and_then(|tag| {
|
||||
return match tag {
|
||||
0 => std::option::Option::Some("create_registry".to_string()),
|
||||
1 => std::option::Option::Some("update_registry".to_string()),
|
||||
_ => std::option::Option::None,
|
||||
};
|
||||
}),
|
||||
observations: std::vec::Vec::new(),
|
||||
diagnostics: std::vec![kb_decoder_api::DecoderDiagnostic {
|
||||
code: "elgamal_registry_wire_invalid".to_string(),
|
||||
message,
|
||||
retriable: false,
|
||||
}],
|
||||
};
|
||||
},
|
||||
};
|
||||
let committed = !input.transaction_failed;
|
||||
let wire_hash = hash(bytes.as_slice());
|
||||
let event = kb_model::DecodedProtocolEvent {
|
||||
signature: kb_model::Signature(input.signature.clone()),
|
||||
slot: kb_model::Slot(input.slot),
|
||||
instruction_path: kb_model::InstructionPath(input.instruction_path.clone()),
|
||||
program_id: kb_model::ProgramId(input.program_id.clone()),
|
||||
protocol_code: kb_model::ProtocolCode(crate::SURFACE_CODE.to_string()),
|
||||
surface_code: kb_model::SurfaceCode(crate::SURFACE_CODE.to_string()),
|
||||
event_code: kb_model::EventCode(format!("{}.{}", crate::SURFACE_CODE, parsed.code)),
|
||||
event_name: kb_model::EventName(parsed.code.to_string()),
|
||||
event_family: kb_model::EventFamily::Admin,
|
||||
source_kind: if input.instruction_path.contains('/') {
|
||||
kb_model::EventSourceKind::InnerInstruction
|
||||
} else {
|
||||
kb_model::EventSourceKind::Instruction
|
||||
},
|
||||
confidence: kb_model::DecoderConfidence::ManualExact,
|
||||
};
|
||||
let observation = kb_decoder_api::DecodedObservation {
|
||||
event_key: format!("elgamal_registry:{}:0", parsed.code),
|
||||
event,
|
||||
payload_json: serde_json::json!({
|
||||
"eventVersion": crate::EVENT_VERSION,
|
||||
"instruction": parsed.code,
|
||||
"wireTag": parsed.tag,
|
||||
"proofInstructionOffset": parsed.proof_instruction_offset,
|
||||
"proofLocationKind": if parsed.proof_instruction_offset == 0 { "context_state_account" } else { "relative_instruction_offset" },
|
||||
"instructionPath": input.instruction_path,
|
||||
"instructionLocation": if input.instruction_path.contains('/') { "inner" } else { "outer" },
|
||||
"transactionSucceeded": !input.transaction_failed,
|
||||
"committed": committed,
|
||||
"accounts": input.instruction_accounts_json,
|
||||
"wire": {
|
||||
"lengthBytes": bytes.len(),
|
||||
"sha256": wire_hash,
|
||||
"prefixHex": hex(bytes.as_slice()),
|
||||
"complete": true,
|
||||
},
|
||||
"programBoundary": {
|
||||
"registryProgramId": kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID,
|
||||
"token2022ProgramId": kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
||||
"zkProofProgramId": kb_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID,
|
||||
"proofDecodedHere": false,
|
||||
}
|
||||
}),
|
||||
transaction_failed: input.transaction_failed,
|
||||
transaction_error: input.transaction_err_json.clone(),
|
||||
observation_committed: committed,
|
||||
proof: kb_decoder_api::DecoderProof {
|
||||
kind: kb_decoder_api::DecoderProofKind::Manual,
|
||||
confidence: kb_model::DecoderConfidence::ManualExact,
|
||||
evidence: std::vec![
|
||||
"spl_elgamal_registry_interface_0_2_1_registry_instruction_unpack".to_string(),
|
||||
format!("wire_tag:{}", parsed.tag),
|
||||
format!("wire_sha256:{wire_hash}"),
|
||||
],
|
||||
},
|
||||
};
|
||||
return kb_decoder_api::DecoderExecutionResult {
|
||||
status: kb_decoder_api::DecoderOutcomeStatus::Decoded,
|
||||
recognized_entry_code: std::option::Option::Some(parsed.code.to_string()),
|
||||
observations: std::vec![observation],
|
||||
diagnostics: std::vec::Vec::new(),
|
||||
};
|
||||
}
|
||||
|
||||
fn hash(bytes: &[u8]) -> std::string::String {
|
||||
let digest = sha2::Sha256::digest(bytes);
|
||||
return digest.iter().map(|byte| return format!("{byte:02x}")).collect();
|
||||
}
|
||||
|
||||
fn hex(bytes: &[u8]) -> std::string::String {
|
||||
return bytes.iter().map(|byte| return format!("{byte:02x}")).collect();
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// file: kb_decoder_spl_elgamal_registry/src/state.rs
|
||||
// version: 2
|
||||
|
||||
//! Strict SPL ElGamal registry account-state parsing.
|
||||
|
||||
use base64::Engine; // rust-rules: trait-import
|
||||
|
||||
/// Exact byte length of one SPL ElGamal registry account.
|
||||
pub const ELGAMAL_REGISTRY_ACCOUNT_LEN: usize = 64;
|
||||
|
||||
/// Public state retained by one SPL ElGamal registry account.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ElGamalRegistryState {
|
||||
/// Wallet address associated with this registry.
|
||||
pub owner: String,
|
||||
/// Exact 32-byte ElGamal public key encoded as standard base64.
|
||||
pub elgamal_pubkey_base64: String,
|
||||
/// Exact account bytes encoded as lowercase hexadecimal.
|
||||
pub wire_hex: String,
|
||||
}
|
||||
|
||||
/// Parse one exact SPL ElGamal registry account.
|
||||
pub fn parse_elgamal_registry_state(
|
||||
data: &[u8],
|
||||
) -> std::result::Result<ElGamalRegistryState, std::string::String> {
|
||||
if data.len() != ELGAMAL_REGISTRY_ACCOUNT_LEN {
|
||||
return std::result::Result::Err(format!(
|
||||
"SPL ElGamal registry account requires exactly {ELGAMAL_REGISTRY_ACCOUNT_LEN} bytes; received {}",
|
||||
data.len()
|
||||
));
|
||||
}
|
||||
return std::result::Result::Ok(ElGamalRegistryState {
|
||||
owner: bs58::encode(&data[..32]).into_string(),
|
||||
elgamal_pubkey_base64: base64::engine::general_purpose::STANDARD.encode(&data[32..64]),
|
||||
wire_hex: lower_hex(data),
|
||||
});
|
||||
}
|
||||
|
||||
fn lower_hex(bytes: &[u8]) -> String {
|
||||
let mut output = String::with_capacity(bytes.len() * 2);
|
||||
for byte in bytes {
|
||||
output.push_str(&format!("{byte:02x}"));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use base64::Engine; // rust-rules: trait-import
|
||||
|
||||
#[test]
|
||||
fn exact_registry_state_preserves_owner_key_and_wire() {
|
||||
let mut data = [0u8; crate::ELGAMAL_REGISTRY_ACCOUNT_LEN];
|
||||
data[..32].fill(7);
|
||||
data[32..].fill(9);
|
||||
let parsed = crate::parse_elgamal_registry_state(&data);
|
||||
assert_eq!(
|
||||
parsed.as_ref().map(|state| return state.owner.clone()),
|
||||
std::result::Result::Ok(bs58::encode([7u8; 32]).into_string())
|
||||
);
|
||||
assert_eq!(
|
||||
parsed.as_ref().map(|state| return state.elgamal_pubkey_base64.clone()),
|
||||
std::result::Result::Ok(base64::engine::general_purpose::STANDARD.encode([9u8; 32]))
|
||||
);
|
||||
assert_eq!(
|
||||
parsed.as_ref().map(|state| return state.wire_hex.len()),
|
||||
std::result::Result::Ok(128)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registry_state_rejects_every_non_exact_length() {
|
||||
assert!(crate::parse_elgamal_registry_state(&[0; 63]).is_err());
|
||||
assert!(crate::parse_elgamal_registry_state(&[0; 65]).is_err());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user