feat(provenance): FIX-4 — verificador attestation on-chain (cierra V3-C1 + squat)
El verificador attestation queda VIVO: una firma producida por el código REAL de Ergon (arkhe-noesis-core, postcard + mensaje domain-separado) verifica byte-fiel en el pallet Substrate. Cierra el FIX-DEFERRED del roadmap (helper de Ergon ya en árbol) + el replay cross-chain V3-C1. origin-chain-types/attestation_verify.rs (nuevo): - ATTESTATION_DOMAIN_TAG + attestation_signed_message: espejo BYTE-IDÉNTICO del oráculo (DC 076d15cc ASP-3). Mensaje = TAG ‖ genesis_hash(32) ‖ payload. - decode_signing_payload: lector postcard SECUENCIAL total y estricto del SigningPayload de Ergon (sin dep nueva en el runtime wasm frágil). Lectores varint u64/u32 con los bounds exactos de postcard (cierra el LOW de fidelidad flagged: verdict/severity/constraints son u32, máx 5 bytes / 5º ≤ 15). Cotas DoS en strings. NUNCA re-serializa (contrato ASP-3). pallet-origin-provenance: - attest_asset(signing_payload: BoundedVec<u8,512>, signature: [u8;64]): recibe los bytes postcard OPACOS + la firma; decodifica, verifica Ed25519 vía sp_io::crypto::ed25519_verify (host function, no bloatea wasm, precedente FIX-3) sobre el mensaje domain-separado con el genesis_hash de ESTA cadena. La pubkey se decodifica del payload (no param aparte). creator = el validador, anchored_at_block real (cerró los placeholders del scaffold). - Dedup-by-BLAKE3 con rechazo EXPLÍCITO (asset_id = BLAKE3 content hash, dentro del payload firmado → replay del mismo blob reusa el asset_id → AlreadyAttested). Hallazgos de la verificación adversarial (workflow ultracode, 3 lentes) cerrados de RAÍZ: - HIGH asset-id squat / provenance poisoning + MEDIUM tier-forgery: el gate era self-attested (level del payload) → cualquiera con una clave Ed25519 anclaba basura institucional bajo el hash PÚBLICO de un asset legítimo y lo envenenaba de forma inmutable. FIX: registro on-chain AuthorizedValidators (pubkey → nivel OIT) gobernado por Root (register_validator/revoke_validator); la membresía es la VERDAD, no el payload. Mismo patrón que TierProfiles de FIX-6 Stage 2a (legitimidad on-chain, no caller-supplied). Sunset a attestation-driven cuando aterrice el registro OIT de Mathi. - MEDIUM método muerto: removido el ValidationAttestation::signing_payload() SCALE divergente (producía bytes que jamás verificarían — footgun). Decisiones (autoridad Pilar 4): sp_io ed25519_verify (consistente FIX-3, estándar Substrate) · dedup-by-asset_id subsume replay exacto · golden vectors generados desde el oráculo REAL de Ergon (seed [7u8;32], genesis 0xAB) = prueba cross-runtime. PENDIENTE (gap legítimo, no bug): OriginChainAttestationSink (impl del trait ValidationAttestationSink de Ergon) es código cliente off-chain — vive en un crate bridge del monorepo (subxt), NO en el chain repo (dependency direction submódulo→monorepo prohibida). Se cablea con la integración Noesis→chain. Tests: 19 provenance (golden vectors cross-runtime + squat cerrado + revoke + anti-replay cross/intra-chain + tamper + malformed) + 72 types (decoder fuzz- equivalente a postcard + fidelidad u32) + 21 suites workspace verde. cargo +1.88. WASM: build VPS-gated (UnknownOpcode 252 ambiental, ver v0.0.58). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3364994cce
commit
ce18e191d0
7 files changed
+769
-475
No files matched your search
@@ -103,7 +103,14 @@ sp_api::decl_runtime_apis! {
|
||||
pub mod pallet {
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
use origin_chain_types::{AssetAttestation, AssetHash, ValidationAttestation};
|
||||
use frame_support::sp_runtime::traits::{SaturatedConversion, Zero};
|
||||
use origin_chain_types::{
|
||||
attestation_signed_message, decode_signing_payload, AssetAttestation, AssetHash,
|
||||
};
|
||||
|
||||
/// Cota DoS del `signing_payload` postcard recibido (el real ~115 bytes;
|
||||
/// 512 es holgado para schema/identifier futuros sin permitir flooding).
|
||||
pub type MaxPayloadLen = ConstU32<512>;
|
||||
|
||||
/// Pallet configuration trait canonical.
|
||||
///
|
||||
@@ -140,6 +147,25 @@ pub mod pallet {
|
||||
pub type Attestations<T: Config> =
|
||||
StorageMap<_, Blake2_128Concat, AssetHash, AssetAttestation, OptionQuery>;
|
||||
|
||||
/// FIX-4 (cierra el HIGH "asset-id squat"): registro on-chain de validadores
|
||||
/// Noesis autorizados → nivel OIT asignado por governance. **La membresía es
|
||||
/// la VERDAD**, no el `level` que el firmante pone en su propio payload
|
||||
/// (que era self-attested → cualquiera con una clave Ed25519 podía anclar
|
||||
/// basura institucional bajo el hash público de un asset legítimo y
|
||||
/// envenenarlo de forma inmutable). Ahora solo una pubkey REGISTRADA puede
|
||||
/// anclar; un validador registrado que ancle basura es revocable y auditable
|
||||
/// (problema de governance, no ataque anónimo). Mismo patrón que
|
||||
/// `TierProfiles` de FIX-6 Stage 2a: legitimidad on-chain, no caller-supplied.
|
||||
///
|
||||
/// **Root-only interim** (sudo Index 12 / governance). Sunset: cuando el
|
||||
/// registro OIT real de Mathi (PerceptionAdapter + DID binding) aterrice,
|
||||
/// migra a auto-registro attestation-driven. Cross-link futuro: AgentIdentity
|
||||
/// (Index 9).
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn authorized_validator_level)]
|
||||
pub type AuthorizedValidators<T: Config> =
|
||||
StorageMap<_, Blake2_128Concat, [u8; 32], u8, OptionQuery>;
|
||||
|
||||
/// PROV-7 canonical: Events emisión canonical.
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
@@ -158,6 +184,19 @@ pub mod pallet {
|
||||
/// Reason canonical de rejection per RejectionReason enum.
|
||||
reason: RejectionReason,
|
||||
},
|
||||
/// FIX-4: validador Noesis autorizado (Root) con su nivel OIT. Auditable
|
||||
/// on-chain: quién quedó habilitado para anclar y a qué nivel.
|
||||
ValidatorRegistered {
|
||||
/// Ed25519 public key del validador autorizado.
|
||||
public_key: [u8; 32],
|
||||
/// Nivel OIT asignado por governance.
|
||||
level: u8,
|
||||
},
|
||||
/// FIX-4: validador revocado (Root) — ya no puede anclar.
|
||||
ValidatorRevoked {
|
||||
/// Ed25519 public key del validador revocado.
|
||||
public_key: [u8; 32],
|
||||
},
|
||||
}
|
||||
|
||||
/// Rejection reasons canonical PROV-7.
|
||||
@@ -204,102 +243,138 @@ pub mod pallet {
|
||||
AlreadyAttested,
|
||||
/// Marihan refinement Phase 2.2 noted canonical.
|
||||
MalformedValidationAttestation,
|
||||
/// FIX-4: la pubkey del firmante NO está en el registro on-chain de
|
||||
/// validadores autorizados (cierra el squat — solo validadores
|
||||
/// registrados por governance pueden anclar).
|
||||
ValidatorNotAuthorized,
|
||||
}
|
||||
|
||||
/// Dispatchables canonical (PROV-3 canonical: attest_asset).
|
||||
/// Dispatchables (PROV-3: attest_asset).
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// PROV-3 canonical: `attest_asset` extrinsic canonical.
|
||||
/// PROV-3 + FIX-4: ancla una attestation Noesis verificada on-chain.
|
||||
///
|
||||
/// Submits a `ValidationAttestation` (Ergon Slice 7.0r struct) para
|
||||
/// anchoring canonical on-chain. Flow canonical 6-step per DC PROV-3:
|
||||
/// El validador firma OFF-CHAIN (con el helper de Ergon
|
||||
/// `attestation_signed_message`) sobre `TAG ‖ genesis_hash ‖ payload`,
|
||||
/// donde `payload` es el `SigningPayload` postcard (Slice 7.0r). El
|
||||
/// extrinsic recibe esos **bytes postcard opacos** + la firma; el pallet
|
||||
/// re-deriva el mensaje y verifica — NUNCA re-serializa (contrato ASP-3
|
||||
/// DC `076d15cc`). El `public_key` se DECODIFICA del payload (no es un
|
||||
/// parámetro aparte: la firma prueba que esa clave firmó esta attestation
|
||||
/// para esta cadena).
|
||||
///
|
||||
/// 1. PROV-4 canonical: Ed25519 signature verification canonical
|
||||
/// 2. PROV-5 canonical: validate `validator_oit.level >= 3` institutional
|
||||
/// 3. PROV-6 canonical: check `is_anchorable()` (verdict + severity)
|
||||
/// 4. PROV-2 canonical: convert ValidationAttestation → AssetAttestation
|
||||
/// 5. PROV-2 canonical: insert StorageMap canonical
|
||||
/// 6. PROV-7 canonical: emit AssetAttested event canonical
|
||||
///
|
||||
/// **SCAFFOLD v0.0.14 canonical**: real impl deferred post Ergon Cap 42+
|
||||
/// sub-crate ship + Ed25519 verification wire-up. Current canonical solo
|
||||
/// validates basic state (origin signed + not already attested) + emite
|
||||
/// placeholder event canonical.
|
||||
/// Flow (gates baratos antes del crypto caro, patrón FIX-3):
|
||||
/// 1. `ensure_signed` — el submitter (relayer/pagador del fee).
|
||||
/// 2. Decode postcard → (asset_id, public_key, level, verdict, severity).
|
||||
/// 3. **Dedup-by-BLAKE3 con RECHAZO EXPLÍCITO**: la storage key ES el
|
||||
/// `asset_id` (= BLAKE3 content hash del asset); un replay del mismo
|
||||
/// attestation firmado reusa el mismo asset_id → `AlreadyAttested`.
|
||||
/// (Decisión Pilar 4 sesión 6+: replay = rechazo explícito, no dedup
|
||||
/// silencioso.) Va ANTES del verify para no gastar crypto en replays.
|
||||
/// 4. FIX-4: gate de legitimidad ON-CHAIN — la pubkey del firmante debe
|
||||
/// estar en `AuthorizedValidators` (registro governance) con nivel
|
||||
/// >= 3. NO se confía en el `level` self-attested del payload (cierra
|
||||
/// el squat + tier-forgery).
|
||||
/// 5. PROV-6: gate anchorable (verdict Passed|Warning, severity Info|Warning).
|
||||
/// 6. FIX-4/V3-C1: Ed25519 verify via `sp_io::crypto::ed25519_verify`
|
||||
/// (host function, no bloatea wasm) sobre el mensaje domain-separado
|
||||
/// con el `genesis_hash` de ESTA cadena (cierra replay cross-chain).
|
||||
/// 7. Insert `AssetAttestation` (creator = validador, block real) + evento.
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(Weight::from_parts(10_000, 0))]
|
||||
#[pallet::weight(Weight::from_parts(20_000, 0))]
|
||||
pub fn attest_asset(
|
||||
origin: OriginFor<T>,
|
||||
validation_attestation: ValidationAttestation,
|
||||
signing_payload: BoundedVec<u8, MaxPayloadLen>,
|
||||
signature: [u8; 64],
|
||||
) -> DispatchResult {
|
||||
// Verify origin canonical signed.
|
||||
let _signer = ensure_signed(origin)?;
|
||||
// 1. Origin signed (submitter — paga el fee; el creator semántico es
|
||||
// el validador cuya clave está dentro del payload firmado).
|
||||
let _submitter = ensure_signed(origin)?;
|
||||
|
||||
let asset_hash = validation_attestation.asset_id;
|
||||
// 2. Decode postcard del SigningPayload real de Ergon (total, DoS-safe).
|
||||
let decoded = decode_signing_payload(&signing_payload)
|
||||
.map_err(|_| Error::<T>::MalformedValidationAttestation)?;
|
||||
let asset_hash = AssetHash::new(decoded.asset_id);
|
||||
|
||||
// PROV-2 idempotency check canonical.
|
||||
// 3. Dedup-by-BLAKE3 (asset_id) — rechazo explícito de replay, antes
|
||||
// del crypto (FIX-3 discipline).
|
||||
ensure!(
|
||||
!Attestations::<T>::contains_key(asset_hash),
|
||||
Error::<T>::AlreadyAttested
|
||||
);
|
||||
|
||||
// PROV-5 OIT level >= 3 enforcement canonical.
|
||||
// 4. FIX-4: legitimidad ON-CHAIN (cierra el squat + tier-forgery).
|
||||
// La pubkey del firmante DEBE estar en el registro de validadores
|
||||
// autorizados; el nivel es el del REGISTRO (governance), NO el
|
||||
// `level` self-attested del payload. Sin esto, cualquiera con una
|
||||
// clave Ed25519 anclaba basura institucional bajo el hash público
|
||||
// de un asset legítimo.
|
||||
let authorized_level = AuthorizedValidators::<T>::get(decoded.public_key)
|
||||
.ok_or(Error::<T>::ValidatorNotAuthorized)?;
|
||||
ensure!(authorized_level >= 3, Error::<T>::ValidatorTierInsufficient);
|
||||
|
||||
// 5. PROV-6: anchorable (Inmutable #26 — una vez anclado es inmutable).
|
||||
ensure!(decoded.is_anchorable(), Error::<T>::NotAnchorable);
|
||||
|
||||
// 6. FIX-4 (V3-C1): re-derivar el mensaje domain-separado con el
|
||||
// genesis_hash de ESTA cadena y verificar Ed25519 sobre los BYTES
|
||||
// recibidos (nunca re-serializar). Host function = no wasm bloat.
|
||||
let genesis_full = <frame_system::Pallet<T>>::block_hash(BlockNumberFor::<T>::zero());
|
||||
let genesis_ref: &[u8] = genesis_full.as_ref();
|
||||
let mut genesis_hash = [0u8; 32];
|
||||
let n = genesis_ref.len().min(32);
|
||||
genesis_hash[..n].copy_from_slice(&genesis_ref[..n]);
|
||||
|
||||
let message = attestation_signed_message(&signing_payload, &genesis_hash);
|
||||
let sig = sp_core::ed25519::Signature::from_raw(signature);
|
||||
let pubkey = sp_core::ed25519::Public::from_raw(decoded.public_key);
|
||||
ensure!(
|
||||
validation_attestation.validator_oit.is_institutional(),
|
||||
Error::<T>::ValidatorTierInsufficient
|
||||
sp_io::crypto::ed25519_verify(&sig, &message, &pubkey),
|
||||
Error::<T>::InvalidSignature
|
||||
);
|
||||
|
||||
// PROV-6 is_anchorable check canonical.
|
||||
ensure!(
|
||||
validation_attestation.is_anchorable(),
|
||||
Error::<T>::NotAnchorable
|
||||
);
|
||||
|
||||
// PROV-4 canonical: Real Ed25519 verification via ed25519-dalek (v0.0.16).
|
||||
//
|
||||
// Pattern canonical:
|
||||
// 1. Reconstruct VerifyingKey desde validator_oit.public_key (32 bytes)
|
||||
// 2. Reconstruct Signature desde attestation.signature (64 bytes)
|
||||
// 3. Compute signing_payload canonical (SCALE encoding interim Option B)
|
||||
// 4. verify_strict canonical (rejects malleable signatures + zero-keys)
|
||||
//
|
||||
// Cuando Ergon arkhe-noesis-attestation-types ship Cap 42+, swap canonical
|
||||
// a postcard encoding canonical sin breaking pallet logic — verify call site
|
||||
// remains identical canonical.
|
||||
let pubkey = ed25519_dalek::VerifyingKey::from_bytes(
|
||||
&validation_attestation.validator_oit.public_key,
|
||||
)
|
||||
.map_err(|_| Error::<T>::InvalidSignature)?;
|
||||
|
||||
let signature = ed25519_dalek::Signature::from_bytes(
|
||||
&validation_attestation.signature,
|
||||
);
|
||||
|
||||
let payload = validation_attestation.signing_payload();
|
||||
|
||||
pubkey
|
||||
.verify_strict(&payload, &signature)
|
||||
.map_err(|_| Error::<T>::InvalidSignature)?;
|
||||
|
||||
// PROV-2 canonical: convert + insert canonical.
|
||||
// SCAFFOLD: minimal AssetAttestation construction (without creator/sig
|
||||
// mapping canonical — that's Ergon sub-crate concern post Cap 42+).
|
||||
// 7. Anclar: creator = el validador (su Ed25519 pubkey), block real.
|
||||
let block_number = <frame_system::Pallet<T>>::block_number();
|
||||
let attestation = AssetAttestation {
|
||||
hash: asset_hash,
|
||||
creator: origin_chain_types::AccountId::new([0u8; 32]), // SCAFFOLD placeholder
|
||||
signature: origin_chain_types::Signature::new(validation_attestation.signature),
|
||||
anchored_at_block: 0, // SCAFFOLD — block_number wire post v0.0.16
|
||||
agent_chain: origin_chain_types::AgentChain::default(), // BoundedVec empty
|
||||
creator: origin_chain_types::AccountId::new(decoded.public_key),
|
||||
signature: origin_chain_types::Signature::new(signature),
|
||||
anchored_at_block: block_number.saturated_into::<u64>(),
|
||||
agent_chain: origin_chain_types::AgentChain::default(),
|
||||
};
|
||||
|
||||
Attestations::<T>::insert(asset_hash, attestation);
|
||||
|
||||
// PROV-7 emit event canonical.
|
||||
let block_number = <frame_system::Pallet<T>>::block_number();
|
||||
Self::deposit_event(Event::AssetAttested {
|
||||
asset_hash,
|
||||
block_number,
|
||||
});
|
||||
Self::deposit_event(Event::AssetAttested { asset_hash, block_number });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// FIX-4: registra (o re-nivela) un validador Noesis autorizado a anclar.
|
||||
/// **Root-only** (sudo Index 12 / governance) — cierra el squat: solo
|
||||
/// validadores que governance habilita pueden anclar. Sunset a
|
||||
/// attestation-driven cuando el registro OIT de Mathi aterrice.
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight(Weight::from_parts(10_000, 0))]
|
||||
pub fn register_validator(
|
||||
origin: OriginFor<T>,
|
||||
public_key: [u8; 32],
|
||||
level: u8,
|
||||
) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
AuthorizedValidators::<T>::insert(public_key, level);
|
||||
Self::deposit_event(Event::ValidatorRegistered { public_key, level });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// FIX-4: revoca un validador — ya no puede anclar. **Root-only.**
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight(Weight::from_parts(10_000, 0))]
|
||||
pub fn revoke_validator(
|
||||
origin: OriginFor<T>,
|
||||
public_key: [u8; 32],
|
||||
) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
AuthorizedValidators::<T>::remove(public_key);
|
||||
Self::deposit_event(Event::ValidatorRevoked { public_key });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -320,96 +395,15 @@ mod tests_integration;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ed25519_dalek::{Signer, SigningKey};
|
||||
use origin_chain_types::{OitIdentity, SeverityTier, ValidationAttestation, ValidationVerdict};
|
||||
|
||||
// Compile-time + Ed25519 verify standalone tests canonical.
|
||||
// Mock runtime integration tests canonical = Phase 2.1 v0.0.17 next slice.
|
||||
|
||||
fn signed_attestation(verdict: ValidationVerdict, severity: SeverityTier) -> ValidationAttestation {
|
||||
let signing_key = SigningKey::from_bytes(&[7u8; 32]);
|
||||
let verifying_key = signing_key.verifying_key();
|
||||
|
||||
let mut att = ValidationAttestation {
|
||||
schema_version: *b"1.0.0 ",
|
||||
asset_id: origin_chain_types::AssetHash::new([1u8; 32]),
|
||||
validator_oit: OitIdentity {
|
||||
public_key: verifying_key.to_bytes(),
|
||||
level: 3,
|
||||
},
|
||||
validated_at_us: 1234567890,
|
||||
verdict,
|
||||
severity,
|
||||
constraints_checked: 42,
|
||||
signature: [0u8; 64], // placeholder, replaced post sign
|
||||
};
|
||||
|
||||
let payload = att.signing_payload();
|
||||
let signature = signing_key.sign(&payload);
|
||||
att.signature = signature.to_bytes();
|
||||
att
|
||||
}
|
||||
// FIX-4: la verificación Ed25519 real (sp_io host function sobre el mensaje
|
||||
// domain-separado, con golden vectors del oráculo de Ergon) vive en
|
||||
// `tests_integration`. El decoder postcard + el mensaje firmado se prueban
|
||||
// en `origin-chain-types::attestation_verify`. Aquí quedan compile-checks.
|
||||
|
||||
#[test]
|
||||
fn ed25519_verify_real_valid_signature_canonical() {
|
||||
// PROV-4 canonical: signed attestation canonical verifies correctamente.
|
||||
let att = signed_attestation(ValidationVerdict::Passed, SeverityTier::Info);
|
||||
let pubkey = ed25519_dalek::VerifyingKey::from_bytes(
|
||||
&att.validator_oit.public_key,
|
||||
)
|
||||
.expect("valid pubkey canonical");
|
||||
let sig = ed25519_dalek::Signature::from_bytes(&att.signature);
|
||||
let payload = att.signing_payload();
|
||||
assert!(pubkey.verify_strict(&payload, &sig).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ed25519_verify_tampered_signature_rejected_canonical() {
|
||||
// PROV-4 canonical: tampered signature canonical rejected.
|
||||
let mut att = signed_attestation(ValidationVerdict::Passed, SeverityTier::Info);
|
||||
// Flip a bit en signature canonical.
|
||||
att.signature[0] ^= 0xFF;
|
||||
let pubkey = ed25519_dalek::VerifyingKey::from_bytes(
|
||||
&att.validator_oit.public_key,
|
||||
)
|
||||
.expect("valid pubkey canonical");
|
||||
let sig = ed25519_dalek::Signature::from_bytes(&att.signature);
|
||||
let payload = att.signing_payload();
|
||||
assert!(pubkey.verify_strict(&payload, &sig).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ed25519_verify_tampered_payload_rejected_canonical() {
|
||||
// PROV-4 canonical: tampered payload canonical (signing_payload mismatch) rejected.
|
||||
let mut att = signed_attestation(ValidationVerdict::Passed, SeverityTier::Info);
|
||||
let original_sig = att.signature;
|
||||
// Flip a bit en asset_id (cambia signing_payload pero mantiene signature original).
|
||||
att.asset_id = origin_chain_types::AssetHash::new([99u8; 32]);
|
||||
let pubkey = ed25519_dalek::VerifyingKey::from_bytes(
|
||||
&att.validator_oit.public_key,
|
||||
)
|
||||
.expect("valid pubkey canonical");
|
||||
let sig = ed25519_dalek::Signature::from_bytes(&original_sig);
|
||||
let payload = att.signing_payload(); // payload canonical NEW (con asset_id tampered)
|
||||
assert!(pubkey.verify_strict(&payload, &sig).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ed25519_zero_signature_fails_verify_canonical() {
|
||||
// PROV-4 canonical: all-zero signature canonical rejected por verify_strict.
|
||||
// (Note: ed25519-dalek `from_bytes` acepta all-zero pubkey/sig — son
|
||||
// valid Edwards points; el rejection canonical pasa en verify_strict.)
|
||||
let signing_key = SigningKey::from_bytes(&[7u8; 32]);
|
||||
let verifying_key = signing_key.verifying_key();
|
||||
let payload = b"any payload canonical";
|
||||
let zero_sig = ed25519_dalek::Signature::from_bytes(&[0u8; 64]);
|
||||
assert!(verifying_key.verify_strict(payload, &zero_sig).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pallet_scaffold_compiles_canonical() {
|
||||
// Scaffold compiles — verified by cargo check.
|
||||
// Real mock runtime integration tests = Phase 2.1 v0.0.17.
|
||||
fn pallet_compiles_canonical() {
|
||||
// Verified by cargo check. E2E del fee-path verify = tests_integration.
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user