feat(identity): UNA identidad para todo Origin — contexto KDF compartido (RV 9→10)

DC ORIGIN IDENTITY UNIFICATION V1 (Larihana), Opción A + enmienda D-ID-7 mía.
Ratificado por David 2026-07-28. Firmado approve (pendientes Larihana + Mathi).

EL PROBLEMA (medido por Larihana, verificado por mí en ambos lados):
  chain: blake3::derive_key("origin-chain/agent-did/v1", pk) → Did
  OCF:   blake3::hash(pk)                                    → OitFingerprint
La MISMA pubkey Ed25519 producía 32 bytes DISTINTOS en cada capa. No era un bug
de criptografía: cada capa hizo domain separation correctamente por separado y
nadie decidió la relación entre las dos. Consecuencia: viendo un fingerprint en
un canal de chat NO se podía resolver el DID on-chain sin la pubkey completa —
un usuario tenía una identidad en el chat y otra en la blockchain. Directiva de
David: "que todo lo que creamos tenga una misma identidad para poder acceder".

EL CAMBIO:
- Nueva constante pública ORIGIN_IDENTITY_KDF_CONTEXT = "origin/identity/v1",
  el contexto CANÓNICO compartido por todas las capas de Origin.
- derive_did pasa a derivar con ese contexto ⇒ el identificador de 32 bytes es
  ahora EL MISMO en chain, chat (OCF), assets y credenciales.
- RUNTIME_VERSION 9→10: 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.

POR QUÉ SIN MIGRACIÓN (la verificación que el DC me delegó, medida no asumida):
- Sin GenesisConfig en el pallet → no se siembran identidades en génesis.
- Sin DIDs en los 3 presets de génesis.
- Sin golden vectors ni DID hex hardcodeado en el repo.
- StorageVersion = 0, nunca migrado — greenfield puro.
- derive_did = UNA función, UN call-site; los tests lo invocan (no pinnean bytes).
⇒ Ningún estado on-chain dependía del esquema viejo. La ventana pre-mainnet
  estaba abierta y el costo fue una constante de string.

ENMIENDA D-ID-7 — golden vectors cross-repo (lo que aporté al DC):
El DC arreglaba la INSTANCIA pero no la CLASE. Origin Chain es submódulo y NO
puede depender de arkhe-fabric-core (dep-direction submódulo→monorepo = ILEGAL,
la misma ley que impide el swap del attestation SSOT), así que las dos capas no
pueden compartir la constante por Cargo — y sin ancla podían volver a divergir en
silencio, que es justo el bug que cerramos. Añadidos 3 vectores pinneados a bytes
(2 patrones + 1 pubkey Ed25519 real) para que fabric-core pinnee LOS MISMOS.
Si cualquiera de los dos lados cambia el contexto o la función, su test falla.
Mismo patrón que ya nos salvó con el attestation SSOT: paridad por matemática,
no por disciplina humana.

NO VACUO — verificado empíricamente: saboteé el contexto a "v2-DRIFT" y AMBOS
tests fallaron con el diagnóstico correcto; restaurado, verdes de nuevo. (Un test
que pasa no prueba nada hasta que se le ve fallar sin el fix.)

TRADE-OFF asumido y dejado explícito en el acta del DC: un identificador único en
chat + chain + assets + credenciales implica que ver el fingerprint de alguien en
un canal permite resolver sus activos y transacciones on-chain — no hay frontera
de seudonimato entre capas. Es INHERENTE a "una identidad para todo"; la
alternativa (derivación por-contexto tipo HD) reintroduce los dos identificadores
en dos pantallas que justamente eliminamos. Decisión consciente, no efecto
secundario descubierto después.

Verificado: workspace completo verde (0 fallos), 20/20 en agent-identity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
davidherranandClaude Opus 4.8 committed 2026-07-28 12:25:05 -05:00
1 parent 266ee4084d
commit 13f8ac00a5
2 files changed
+130 -11

No files matched your search

+18 -8
View File
@@ -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);
}