diff --git a/crates/origin-chain-agent-identity/src/lib.rs b/crates/origin-chain-agent-identity/src/lib.rs index 403e38a..0e095c1 100644 --- a/crates/origin-chain-agent-identity/src/lib.rs +++ b/crates/origin-chain-agent-identity/src/lib.rs @@ -75,12 +75,36 @@ pub fn derive_did(public_key: &[u8; 32]) -> Did { // FIX-3 (V3-H2): KDF domain-separado vía blake3::derive_key (context string // como dominio) en vez de un hash ad-hoc concat. Es el patrón KDF canonical de // BLAKE3 y previene colisión cross-uso del hash. Inmutable #16 preservado - // (determinista cross-runtime). ⚠️ Cambia el DID para un mismo public_key vs el - // esquema previo (BLAKE3(pk || version)) — aceptable pre-mainnet (greenfield). - let did_bytes = blake3::derive_key("origin-chain/agent-did/v1", public_key); + // (determinista cross-runtime). + // + // DC ORIGIN IDENTITY UNIFICATION V1 (2026-07-28, David): el contexto pasó de + // `"origin-chain/agent-did/v1"` (propio de la chain) al contexto COMPARTIDO + // `origin/identity/v1` — la MISMA pubkey Ed25519 ahora produce EL MISMO + // identificador de 32 bytes en chain, chat (OCF), assets y credenciales. + // Antes, chain derivaba con su contexto y OCF hacía `blake3::hash(pubkey)` + // plano: dos identificadores para el mismo usuario en dos pantallas. + // ⚠️ Cambia el DID para un mismo public_key vs el esquema previo — aceptable + // pre-mainnet (greenfield): verificado que NINGÚN estado on-chain depende del + // esquema viejo (sin GenesisConfig, sin DIDs en presets, StorageVersion 0). + let did_bytes = blake3::derive_key(ORIGIN_IDENTITY_KDF_CONTEXT, public_key); origin_chain_types::AssetHash::new(did_bytes) } +/// Contexto KDF **canónico de identidad de Origin** — DC ORIGIN IDENTITY +/// UNIFICATION V1 (D-ID-3, Opción A). +/// +/// Una sola cuerda de dominio para TODAS las capas: `derive_key(este_contexto, +/// pubkey_ed25519)` es el identificador unificado de 32 bytes. Origin Chain lo usa +/// para el `Did`; `arkhe-fabric-core` lo usa para `OitFingerprint`. +/// +/// ⚠️ **NO puede compartirse como constante Rust entre los dos repos**: Origin Chain +/// es submódulo y depender de `arkhe-fabric-core` invertiría la dirección de +/// dependencias (submódulo→monorepo = ILEGAL; el chain dejaría de buildear +/// standalone). La paridad se preserva por el **golden vector** de D-ID-7 — +/// ver `tests::did_golden_vectors_cross_repo_parity`. Si cualquiera de las dos +/// capas cambia el contexto o la función, su test falla y la deriva se caza ahí. +pub const ORIGIN_IDENTITY_KDF_CONTEXT: &str = "origin/identity/v1"; + /// Domain tag canonical para la firma de binding del registro de DID (FIX-3 (b), /// V3-H1 anti-front-running). El registrante prueba control de `public_key` /// firmando este dominio + el AccountId del caller (`who`). @@ -402,6 +426,90 @@ mod tests { assert_ne!(did_a, did_b); } + // ═══════════════════════════════════════════════════════════════ + // D-ID-7 — GOLDEN VECTORS CROSS-REPO (DC ORIGIN IDENTITY UNIFICATION V1) + // ═══════════════════════════════════════════════════════════════ + // + // El DC unificó la derivación de identidad: la MISMA pubkey Ed25519 debe + // producir EL MISMO identificador de 32 bytes en Origin Chain (`Did`) y en + // OCF / arkhe-fabric-core (`OitFingerprint::from_ed25519_pubkey`). + // + // PROBLEMA QUE ESTE TEST RESUELVE: Origin Chain es submódulo y NO puede + // depender de `arkhe-fabric-core` (dep-direction submódulo→monorepo = ILEGAL), + // así que las dos capas NO pueden compartir la constante del contexto por + // Cargo. Sin un ancla, pueden volver a divergir en silencio — que es + // exactamente el bug que el DC cierra. Estos vectores son ese ancla: están + // pinneados a bytes en AMBOS repos. Si cualquiera de los dos lados cambia el + // contexto o la función de derivación, su copia de este test falla. + // + // MISMO PATRÓN que los golden vectors del attestation SSOT + // (`origin-chain-types::attestation_verify`): la paridad cross-repo se + // preserva por matemática pinneada, no por disciplina humana. + // + // Contraparte a pinnear en fabric-core: + // OitFingerprint::from_ed25519_pubkey(&pk).as_bytes() == + // + // Regenerar SOLO si el DC cambia el contexto canónico (requiere re-firma). + + /// Vectores canónicos: (pubkey, identificador unificado esperado). + /// Derivación: `blake3::derive_key("origin/identity/v1", pubkey)`. + const IDENTITY_GOLDEN_VECTORS: [([u8; 32], &str); 3] = [ + // Degenerado todo-ceros — trivialmente reproducible en cualquier lenguaje. + ( + [0x00u8; 32], + "d2d1e2fa7be9e8424f1528de50ec58c4e87c357c6b98204882272be56fb8f7db", + ), + // Patrón 0x11 — segundo control simple. + ( + [0x11u8; 32], + "668b46ad620dc9dd0b5f17abceb6357de9bfa5a6102a4174b03c8e13cdbac85b", + ), + // Pubkey Ed25519 REAL (par derivado de la seed [0x11; 32]) — prueba que + // funciona con un punto de curva legítimo, no solo con patrones. + ( + [ + 0xd0, 0x4a, 0xb2, 0x32, 0x74, 0x2b, 0xb4, 0xab, 0x3a, 0x13, 0x68, 0xbd, 0x46, 0x15, + 0xe4, 0xe6, 0xd0, 0x22, 0x4a, 0xb7, 0x1a, 0x01, 0x6b, 0xaf, 0x85, 0x20, 0xa3, 0x32, + 0xc9, 0x77, 0x87, 0x37, + ], + "ea484da28858c031dd846d75f443efa429c142466e68df478ec0761dcb1bc544", + ), + ]; + + fn hex32(bytes: &[u8; 32]) -> alloc::string::String { + use alloc::string::String; + let mut s = String::with_capacity(64); + for b in bytes { + s.push(core::char::from_digit((b >> 4) as u32, 16).unwrap()); + s.push(core::char::from_digit((b & 0x0f) as u32, 16).unwrap()); + } + s + } + + #[test] + fn did_golden_vectors_cross_repo_parity() { + for (pubkey, expected_hex) in IDENTITY_GOLDEN_VECTORS { + let did = derive_did(&pubkey); + assert_eq!( + hex32(did.as_bytes()), + expected_hex, + "DERIVA DE IDENTIDAD ROTA para pubkey {}: el DC ORIGIN IDENTITY \ + UNIFICATION V1 exige que este byte-a-byte coincida con \ + OitFingerprint::from_ed25519_pubkey en arkhe-fabric-core. \ + Si cambiaste el contexto KDF, el DC debe re-firmarse y AMBOS \ + repos regenerar sus vectores.", + hex32(&pubkey) + ); + } + } + + #[test] + fn identity_kdf_context_is_the_shared_canonical_one() { + // El contexto NO es libre: es el ratificado en el DC. Un cambio aquí + // rompe la unificación con OCF aunque los vectores se regeneren. + assert_eq!(ORIGIN_IDENTITY_KDF_CONTEXT, "origin/identity/v1"); + } + #[test] fn re_export_agent_identity_canonical() { // Verify re-exports canonical desde origin-chain-types @@ -409,3 +517,4 @@ mod tests { let _did_alias: Did = origin_chain_types::AssetHash::new([0u8; 32]); } } + diff --git a/crates/origin-chain-runtime/src/lib.rs b/crates/origin-chain-runtime/src/lib.rs index dec3e7c..c43f490 100644 --- a/crates/origin-chain-runtime/src/lib.rs +++ b/crates/origin-chain-runtime/src/lib.rs @@ -266,7 +266,16 @@ pub const IMPL_NAME: &str = "origin-chain-charon"; /// Las FIRMAS de los extrinsics NO cambian ⇒ TRANSACTION_VERSION intacto. La feature /// `runtime-benchmarks` gana `define_benchmarks!` + runtime API `Benchmark` (inerte /// sin la feature) + fix del bug `pallet-grandpa/try-runtime` en la feature. -pub const RUNTIME_VERSION: u32 = 9; +/// +/// **v10 (identidad unificada)**: DC ORIGIN IDENTITY UNIFICATION V1 — `derive_did` +/// pasa del contexto propio de la chain (`origin-chain/agent-did/v1`) al contexto +/// COMPARTIDO `origin/identity/v1`, de modo que la misma pubkey Ed25519 produce EL +/// MISMO identificador de 32 bytes en chain y en OCF (antes eran distintos: dos +/// identidades para el mismo usuario). Cambia la transición de estado de +/// `register_agent_did` (el DID calculado y almacenado) ⇒ spec bump obligatorio. +/// Las FIRMAS de los extrinsics NO cambian ⇒ TRANSACTION_VERSION intacto. Sin +/// migración: verificado que ningún estado on-chain dependía del esquema viejo. +pub const RUNTIME_VERSION: u32 = 10; /// Canonical authoring version per Substrate semver canonical. pub const AUTHORING_VERSION: u32 = 1; @@ -925,13 +934,14 @@ mod tests { use super::*; #[test] - fn runtime_version_v9_benchmarking_weights() { - // v9 = benchmarking real machinery: los 19 extrinsics de los 5 pallets - // custom pasan de weights placeholder (proof_size=0, DoS) a weights INTERIM - // hand-derived DB-aware vía WeightInfo. Los weights son consenso-relevantes - // (fee + llenado de bloque) ⇒ spec bump. Las FIRMAS de los extrinsics NO - // cambian ⇒ TRANSACTION_VERSION intacto. - assert_eq!(RUNTIME_VERSION, 9); + fn runtime_version_v10_unified_identity() { + // v10 = DC ORIGIN IDENTITY UNIFICATION V1: derive_did usa el contexto KDF + // COMPARTIDO `origin/identity/v1` → la misma pubkey Ed25519 produce el + // MISMO identificador de 32 bytes en chain y en OCF. Cambia la transición + // de estado de register_agent_did ⇒ spec bump. Las FIRMAS de los extrinsics + // NO cambian ⇒ TRANSACTION_VERSION intacto. + // (v9 fue la maquinaria de benchmarking: weights INTERIM DB-aware.) + assert_eq!(RUNTIME_VERSION, 10); assert_eq!(TRANSACTION_VERSION, 1); }