46 KiB
Changelog
Cambios notables de la librería Dart. El proyecto usa versionado semántico; mientras sea 0.x, no hay promesa de estabilidad.
Especificación 0.11, en la rama v0.11 — sin versión
Etapa 6a: la escritura de age (06-10-2026)
-
La escritura de los ficheros
age(lib/src/age_writer.dart), port deage.Encrypt,internal/formateinternal/streamdefilippo.io/agev1.3.2, con sus comprobaciones y sus textos:AgeEncryptoryageEncryptsacan la file key, envuelven la file key para cada recipient, en su orden y con sus etiquetas, calculan el MAC de la cabecera y sacan el nonce. Rechazan, con el texto de Go, una lista sin recipients, unas etiquetas que no se pueden mezclar, un recipient que no envuelve la file key y unos stanzas que no se pueden escribir;AgePayloadEncryptorcifra el STREAM según llega el texto, como elEncryptWriterde Go: en chunks de 64 KiB, con el último completo cuando el texto es un múltiplo de 64 KiB distinto de cero y vacío solo cuando el texto lo es, y con el texto de Go trasclose;- las longitudes de un fichero salen, antes de escribirlo, de la de su texto y de la forma de sus stanzas:
ageStreamLength,ageStanzaLength,ageHeaderLengthyageFileLength.
-
Los recipients (
lib/src/recipient.dart):X25519Recipient, con las cadenasage1…y los textos deParseX25519Recipient, ycheckX25519Recipient, las reglas del §37 con los textos deagewrap;ScryptRecipient, con su factor de trabajo y su etiqueta al azar;TimeRecipient, el de tlock, con la etiquetadatekeys-tlock-…deagewrap;generateX25519Identity,rawX25519Identity,rawX25519Recipienty la longitud de cada stanza.
-
La fuente de lo aleatorio (
lib/src/random.dart):RandomSource, inyectable;secureRandom, la de por defecto, conRandom.secure; ySeededRandomSource, determinista, el keystream de ChaCha20 bajo el SHA-256 de una semilla, para pruebas y vectores. Todo lo aleatorio de un fichero sale de ella, en el orden de Go, sigma de tlock incluido:encryptOnG2ywrapTlockStanzala reciben, y ya no usan unRandom.securepropio. Para los 16 huecos de la parte 6b trae ademásrandomIndex, comocrypto/rand.Int, ypermute, como el decapsule.Encrypt. -
Vectores de Go:
tool/age_writer_go_vectors.gocorre en una exportación dedatekeys-goy hace quecrypto/randlea el keystream deSeededRandomSource. Asíage.Encrypt, con los recipients X25519, scrypt y tlock reales, saca valores conocidos, yage_writer.jsonguarda cada uno, con su tamaño y su orden, junto a los ficheros:- X25519 sobre textos de 0, 1, 64 KiB − 1, 64 KiB, 64 KiB + 1, 128 KiB y más bytes, hasta 3 MiB;
- dos, tres y dieciséis recipients X25519, y uno con el bit 255 a 1, que
ageacepta; - scrypt con factores de trabajo de 1 a 16;
- el stanza tlock de rondas de 1 a 11 cifras.
Cada fichero se comprueba con
testkit.SealAge, que lo sella otra vez con la primera y la última extracción como file key y nonce, y Go lo abre. También guarda los errores deage.Encrypty de los constructores,ParseX25519Recipient,CheckX25519Recipient,GenerateX25519Identity,crypto/rand.Int, elpermutedecapsuley las longitudes detestkitycapsule.age_writer.g.dartlleva el mismo JSON para Node.js. -
En la otra dirección:
tool/age_interop_dart_samples.dartescribe los ficheros de las recetas detest/age_interop_support.dart, ytool/age_interop_go_verdicts.golos abre conagey las identities deagewrapy escribeage_interop.json. Son X25519 de 0 a 3 MiB, uno escrito en trozos, tres y dieciséis recipients, tlock abierto con el release de la ronda 1000 de los fixtures, tlock sobre dieciséis X25519 como unSEALED_CONTROLy scrypt con los factores de trabajo 10 y 16. Las pruebas escriben cada fichero otra vez, y debe ser el que leyó Go; Go lo abrió y sacó el texto de la receta, o lo rechazó con la identity que no debía abrirlo; y esta librería hace con él lo mismo que Go, con los mismos textos y las mismas reglas de stanzas. -
Pruebas: 98 nuevas en la VM y 80 en Node.js: 1670 y 412 en total. En Node.js corren los mismos vectores sin los casos caros: los ficheros de megabytes, scrypt con logN 16 y la mayoría de los de tlock.
-
Fallos inyectados, uno a uno y revertidos: los ocho del encargo, el sexto y el séptimo en dos formas. Las pruebas los detectan todos, en la VM y en Node.js: el flag del último chunk en uno que no lo es; un chunk vacío al final tras chunks llenos; un contador del nonce que no avanza; la sal de HKDF en el orden inverso; el MAC de la cabecera sobre los stanzas en otro orden; un recipient de orden bajo aceptado, al envolver la file key y en
checkX25519Recipient; un recipient scrypt junto a uno X25519, sin comparar las etiquetas y con un scrypt sin etiqueta; y la misma clave efímera en varios stanzas. -
tool/age_writer_bench.dartmide el escritor. En la VM, 64 MiB en streaming tardan 1,4 s, unos 46 MiB/s; compilado a JavaScript, 16 MiB tardan 0,33 s, unos 49 MiB/s. La cabecera dePAYLOAD_AGEcuesta 4 ms;INNER_ACCESS_AGE, con 16 stanzas, unos 45 ms en la VM y 35 ms en Node.js;OUTER_TIME_AGE, 40 ms y 0,55 s; y un fichero de clave de autor, con scrypt de logN 16, 0,6 s y 0,85 s. Las cifras, en el README.
Etapa 5c: la firma de alg 2 y el sello de seal_type 2 en los veredictos (06-10-2026)
-
Los veredictos de la firma con certificados y del sello RFC 3161 (
lib/src/securitycms.dart), port deevaluateCMS,signerLineyevaluateSealdesignature2.godedatekeys-goenc531e93, el borrador v0.12, sobre el lector de CMS de la 5a, con el mismo orden de comprobaciones:SIGNERScon su perfil, de 1 a 16 entradas en orden estricto, y después laSignedData: un fallo de cualquiera de los dos es F1;- cada firmante exigido, en el orden de
SIGNERS, y cada ajeno, en el orden de la codificación, con su resultado: válido, inválido, ausente, no verificable, sin sello, con el sello inválido o con el certificado fuera de validez en t, el instante de su sello, con los dos extremos incluidos; - F2 si un exigido es inválido, F5 si falta algo o hay una clave 3, y F6 si todos son válidos y están sellados, con el
Detailde los firmantes; - el sello sobre
SEAL_SUBJECT: S2 por la forma, S1 por un algoritmo fuera de la tabla o unmessageImprintque no es SHA-256, S3 si no verifica, y S4 o S5 con la autoridad y t; - un
round_timeen el instante cero de Go, 0001-01-01T00:00:00Z, cuenta como ninguno, comoIsZero, yVerdicts.sealedAtno cuenta un sello en ese instante, comoSealedAt.
-
El evaluador por defecto lo evalúa todo como Go.
cmsReaderes elcmspor defecto deevaluateSecurity, y con él el deevaluateSecurityInputy de la apertura: nada que Go evalúe queda sin evaluar. Concms: null, la firma dealg2 y el sello deseal_type2 siguen quedando sin evaluar. -
lib/datekeys.dartexportaencodeSignersymaxSigners, comoEncodeSignersyMaxSignersdel paquetecapsulede Go, ycmsReader.cms.dartsigue interno. -
Vectores de Go:
tool/security_go_vectors.goescribe ademássecuritycms_vectors.json: 760 áreas cuya firma CMS o sello RFC 3161 hace el propio generador, como los haceinternal/cms/cmstestpara las pruebas de la referencia, con los veredictos deEvaluateSecurityIn, las líneas, el detalle de cada firmante y del sello y el sello más temprano:- un firmante exigido de cada resultado junto a ajenos de cada resultado, sin
round_time, al abrir en el instante de los sellos, en el contexto de otro head y con una clave 3; tres firmantes exigidos, sacados de una semilla;SIGNERSde 16 y 17 entradas; - la validez de un certificado en el instante de su sello, al nanosegundo, de un
UTCTimea unGeneralizedTimey con diez cifras de fracción, y la de la autoridad en el de su token; - t más la precisión frente a
round_time, a un nanosegundo de cada lado, con cada forma de la precisión, en el instante cero de Go y en el último segundo de 9999; - un sello de cada veredicto junto a una firma de cada veredicto, y mutaciones de la
SignedData, deSIGNERSy de los tokens.
cmstestno se puede importar desde fuera del árbol dedatekeys-go: el generador reescribe la parte que necesita. Las claves salen de etiquetas, ECDSA firma con el nonce del RFC 6979 y RSA con PKCS #1 v1.5, así que el fichero sale igual en cada ejecución, ysecurity_vectors.jsonsale como antes.securitycms_vectors.g.dartlleva, para Node.js, una parte de los casos, con cada par de veredictos de cada grupo, y los fixturesformat3_signed_cmsyformat3_sealed. - un firmante exigido de cada resultado junto a ajenos de cada resultado, sin
-
Pruebas. Todo se compara con Go:
- los 135 casos de
security_cms.json, con el resultado de cada firmante, el sello y las líneas, sin ningún carácter de control ni bidireccional; - los 24 de
security.json, ya enteros; - las 56 firmas de
alg2 y los 105 sellos deseal_type2 desecurity_vectors.jsonque la 5b dejaba sin evaluar; - los 760 casos de
securitycms_vectors.json, con su detalle y su sello más temprano; - y los fixtures
format3_signed_cmsyformat3_sealed: su área frente a su registro, y su apertura con cada credencial deopen_cases.json.
En Node.js corren una parte de los vectores y la apertura entera de los dos fixtures. 150 pruebas nuevas en la VM y 10 en Node.js: 1572 y 332 en total.
- los 135 casos de
-
Fallos inyectados, uno a uno y revertidos: los ocho del encargo, el primero en dos formas, y tres más. Las pruebas los detectan todos, en la VM y en Node.js: un certificado comprobado con el reloj de la apertura, y otro en
round_time, en lugar de en el instante de su sello; un sello aceptado con t más la precisión después deround_time; F6 sin que todos los firmantes sean válidos; más de 16 firmantes aceptados; el aviso de los sellos ausente de las líneas de F6; un nombre de 65 puntos de código mostrado; unmessageImprintque no es SHA-256 aceptado; el resultado de un firmante ajeno en inglés; los firmantes ajenos en el orden inverso; un sello de la clave 3 comprobado sin elSIG_PARTde la clave 2; y una clave 3 junto a una firma dealg2 ignorada. Dos se escapaban al principio en Node.js, más de 16 firmantes y el orden de los ajenos, porque sus casos solo los leían las pruebas de la VM: la parte de Node.js lleva ahoraSIGNERSde 16 y 17 entradas y dos firmantes ajenos. -
tool/open_bench.dartabre también los dos fixtures y mide, dentro de cada apertura, la evaluación de su área de seguridad: en la VM, una apertura tarda unos 55 ms, de los que el área son 9,4 ms con la firma de dos firmantes sellados y 8,5 ms con la dealg1 y el sello; compilada a JavaScript, unos 0,9 s, de los que el área son 0,13 y 0,05 s. Las cifras, en el README.
Etapa 5a: el lector de CMS, ECDSA y RSA (05-10-2026)
- El lector de las firmas CMS y de los sellos RFC 3161 (
lib/src/cms.dart), port deinternal/cmsdedatekeys-goenc531e93, con el perfil de certificado del borrador v0.12 (§29.7, §29.10, §29.11), el mismo orden de comprobaciones y los textos de Go:parseCertlee un certificado campo a campo, sin una librería de X.509;Certda el titular, degivenNameysurnameantes que delcommonName, el emisor, delorganizationNamesi no hay uncommonNamecon texto, yvalidAt, con los dos extremos incluidos;parseSignaturelee una firma separada: elContentInfo, los certificados, las respuestas OCSP, los atributos firmados y sin firmar y cadaSignerInfo, con su certificado;SignerInfo.checkdaCmsResult.valid,invalidonotVerifiablecon la tabla cerrada de algoritmos, y una clave de otro esquema que el algoritmo esinvalid;parseTokenlee un token y su TSTInfo campo a campo, con los errores de forma antes que los de algoritmo, yToken.checklo verifica sobre lo sellado: unmessageImprintde otra longitud no vale, y la precisión llega hasta 2³¹ − 1 segundos;- los identificadores de objeto se comparan por sus bytes, y un SET OF puede repetir un elemento.
- ECDSA (
lib/src/ecdsa.dart,nist_curves.dart) en P-256, P-384 y P-521, comoecdsa.VerifyASN1de Go, y RSA (lib/src/rsa.dart), PKCS #1 v1.5 y PSS comorsa.VerifyPKCS1v15yrsa.VerifyPSS, de código propio sobreBigInt, que no es de tiempo constante: solo verifican, con valores públicos. Los hashes son los depackage:crypto. - Vectores de Go:
tool/cms_go_vectors_test.gocorre como una prueba de Go en una exportación dedatekeys-go, porque importainternal/cms, y contesting/cryptotesthace deterministas las claves y las firmas. Escribecms_ecdsa.json,cms_rsa.json,cms_certs.json,cms_signatures.json,cms_algorithms.json,cms_tokens.json,cms_mutations.jsonycms_corpus.json: las pruebas deinternal/cmscon sus resultados y textos, identificadores cuyos arcos dan la vuelta en 32 o 64 bits, certificados, firmas y tokens editados nodo a nodo y bit a bit, y cada firma y cada token desecurity_cms.jsony de los fixturesformat3_signed_cmsyformat3_sealeddel borrador v0.12.cms_vectors.g.dartlleva una parte de cada uno para Node.js. - Pruebas. 44 nuevas en la VM y 17 en Node.js. Integrada encima de la 5b, el total es de 1422 pruebas en la VM y 322 en Node.js.
- Fallos inyectados, uno a uno y revertidos: un identificador comparado como texto, un SET OF estrictamente ascendente, el titular tomado primero del
commonName, una s de ECDSA sin comparar con el orden, el relleno de RSA sin comprobar su longitud (y, aparte, la longitud de la firma), unmessageImprintde otra longitud aceptado, una precisión de 2³¹ segundos aceptada y un certificado válido un segundo fuera de su periodo. Las pruebas de la VM los detectan todos; las de Node.js, todos menos los de RSA y la precisión, cuyos casos no van en la parte de Node.js. tool/cms_bench.dartmide ECDSA, RSA y el lector: en la VM, una verificación de P-256 tarda 2,4 ms y una de RSA-2048, 0,10 ms. Las cifras, en el README.
Etapa 5b: los compromisos, SECURITY_CBOR, la firma de alg 1 y los veredictos (05-10-2026)
testdata/de la ramav0.12dedatekeys-go, enc531e93, cuyotestdata/es el de601e6d2, el dedatekeys-ts: 133 ficheros, que dicen aún"spec": "0.11", como elSpecVersionde Go, hasta que el autor apruebe el borrador. Frente al tagspec-v0.11, trae los fixturesformat3_unsignedyformat3_note,format3_seal_unsupportedconseal_type4294967295, los registros nuevos deformat3_sealedyformat3_signed_cms, el corpus de 218 casos,note.json,security.jsoncon su contexto y sus líneas,security_cms.jsonde 135 casos ylocator.json, que es de la etapa 7.- Los generadores de
tool/que leen eltestdata/lo leen otra vez:mutation_texts.json,open_cases.json,formats_*.jsonconformats_vectors.g.dart,ibe_vectors.jsonyage_fixtures.jsoncambian;release_vectors.json,primitives.jsony las vistas deopen_vectors.g.dartsalen iguales.age.jsonno lee eltestdata/y queda congelado. - Las pruebas cuentan 26 fixtures y 218 casos, la inspección de
format3_noteda la nota de su registro, y los 16 casos denote.jsondan el resultado y el detalle de Go. Ningún caso descubrió una diferencia con Go: la librería no cambia.
- Los generadores de
- Los compromisos (
lib/src/author.dart), port designature.go:payloadCommit,controlCommitsobreCONTROL_SIGen cada formato,headDigest,signersDigest,authorMessagecon su prefijo y sus 99 bytes,authorCode, que toma los bytes como Go,sigPartysealSubject. SECURITY_CBOR(lib/src/security.dart), port deformat3.goy designature.go: el mapa exterior,author-signatureysealcon los esquemas y los límites de Go, sus codificadores y sus lectores, yevaluateSecurity, que nunca lanza:- X para un mapa exterior que falla su capa 2 o 3, la versión 2 incluida;
- F0 a F4 para la firma, con
verifyStrictenalg1 y la clave buscada entre las guardadas por su cadenadkauthor1…, y S0 a S2 para el sello; - un fallo dentro de una parte es de esa parte, F1 o S2, como Go recupera un panic;
- sin contexto, lee como un lector de la v0.10.
- La frontera con el lector de CMS de la parte 5c es
CmsEvaluator, conevaluateSignatureyevaluateSeal, comoevaluateCMSyevaluateSealde Go. Sin él, la firma dealg2 y el sello deseal_type2 en un contexto quedan sin evaluar, en null, nunca con un veredicto supuesto; la otra parte se evalúa igual.holderText, el nombre de un certificado del §29.7, ya está aquí. - Los veredictos (
lib/src/verdicts.dart) con los textos del borrador v0.12:Verdict.text,Verdicts.lines, con los nombres entre « y », la autoridad de cada sello en las líneas de F6 con su aviso, los resultados en español y t en RFC 3339 con su fracción, yVerdicts.sealedAt, conDetail,SignerLineySignerResult.Verdictspuede tener evaluada una parte sola. Partir las líneas en filas con↳es de la CLI de Go, no de su librería, y no se porta. - La apertura evalúa el área como Go:
OpenOptions.evaluatores ahoraevaluateSecurityInput, consecurityContext, elnewSecurityContextde Go con elhead_digest.format3_signed,format3_unsigned,format3_signature_unsupported,format3_seal_unsupported,format3_security_v2yformat3_notedan al abrirse los veredictos y las líneas de Go;format3_signed_cmsyformat3_sealed, la parte que no necesita el lector de CMS. lib/datekeys.dartexportaauthor.dartysecurity.dart, como el paquetecapsulede Go.- Vectores de Go:
tool/security_go_vectors.go, en el contexto del módulo dedatekeys-goenc531e93, escribesecurity_vectors.json: los compromisos, también del control de cada fixture en cada formato; los codificadores; 1730 evaluaciones deEvaluateSecurityInen 23 contextos y sin contexto, del mapa exterior, deauthor-signaturey desealrotos de todas las formas de sus esquemas y en sus límites, de firmas dealg1 válidas e inválidas, también los casos de «Taming the many EdDSAs» hechos sobreAUTHOR_MESSAGEbuscando el contexto, y de 1500 mutaciones de una semilla fija; las líneas ySealedAtde veredictos con cada detalle; yholderText, al que llega congo:linkname.security_vectors.g.dartlleva todo salvo siete de cada ocho evaluaciones, para Node.js;tool/open_go_vectors.goguarda enopen_cases.jsonlos veredictos de cada cápsula del formato 3 que se abre, con sus líneas, y abre los fixtures firmados conalg1 también con su clave guardada, F3, y con otra, F4.
- Pruebas. Las 1730 evaluaciones de Go, con sus veredictos, sus líneas,
algyseal_typey las partes del lector de CMS; los 24 casos desecurity.json, 23 enteros y uno sin su sello deseal_type2; los compromisos, la firma y el sello de cada fixture del formato 3 frente a su registro; los veredictos de cada apertura deopen_cases.jsony de los 11 casos del corpus que se abren; y la frontera con un lector de CMS de prueba. 58 pruebas nuevas en la VM y 14 en Node.js: 1378 y 305 en total. - Fallos inyectados, uno a uno y revertidos: 12, y las pruebas los detectan todos en la VM y en Node.js: los compromisos de D y de
SEAL_SUBJECTen otro orden, el prefijo deAUTHOR_MESSAGEcambiado, una clave de orden pequeño aceptada, F4 con la clave guardada y F3 con la etiqueta de otra clave, un evaluador que falla la apertura, la línea de F0 y el resultado de un firmante en inglés, un nombre de 65 puntos de código mostrado, y F1 o S1 supuestos paraalg2 oseal_type2 sin el lector de CMS.
Etapa 4c: el head, la nota pública, la inspección y la apertura (05-10-2026)
- La nota pública del §24.1 (
lib/src/note.dart), port deCheckNote,NewNoteyNotedeextensiondedatekeys-goenc531e93:checkNoteycheckNoteData, con la longitud, el UTF-8 y las reglas de texto del autor declarado en ese orden y con los textos de Go;newNote,publicNoteyunusableNote, yHeader.publicNoteyHeader.unusableNote.StandardExtensionscomprueba ya la nota como elStandardde Go, y su parámetrovalidateNotedesaparece. - El head del formato 3 (
lib/src/head.dart):decodeHeaden las capas del §69.1, con R1 y R8 en la tercera, sobre los bytes UTF-8 de las rutas, nunca sobre sus unidades UTF-16; las reglas de rutas, del comentario y del autor depathruleen la cuarta, comoERR_HEAD_INVALIDcon el texto de Go; la maquetación de los ficheros por restas; las extensiones críticas del head;checkHeadEnd; yencodeHead. - La inspección, pasos 1 a 8 (
lib/src/inspect.dart):inspectCapsuleeinspectCapsuleSource,Inspectioncon la comprobación de cada paso y su detalle, como elInspectionde Go;inspectedLength, para leer de un fichero grande solo su principio;maxAccessKeyRead; yinspectVieweinspectJson, la salida exacta dedatekeys inspect -json. - La apertura, pasos 9 a 18 (
lib/src/open.dart,open3.dart):openCapsule, de una cápsula en memoria, yopenCapsuleSource, de unaByteSourceque se lee por tramos (lib/src/source.dart);OpenOptionsyOpened, como los de Go.- La
.dkkdel paso 9.a, decodificada o todavía codificada, con su material, sus extensiones críticas, sucapsule_idy sucapsule_digest, calculado sobre la fuente por tramos; las identities X25519 del llamador y la llave de palabras como una identity más. - El release con la regla del paso 9, nunca antes de su ronda, y su verificación en el paso 10;
OUTER_TIME_AGEcon el stanza tlock;INNER_ACCESS_AGEcon las reglas de los huecos;CONTROL_CBOR,header_binding,I_PAYLOADy P. PAYLOAD_AGEen streaming: el contenido de los formatos 1 y 2 a unByteSinksegúnageautentica cada chunk, con el relleno del formato 2 comprobado y nunca entregado; y en el formato 3 la trama deBODY, el área, el head y cada fichero a unFileSink, con su SHA-256, en el orden y con la precedencia del §63 (lib/src/sink.dart, conMemoryByteSinkyMemoryFileSink).- Cada fallo con el código, el paso y el texto de Go, también los de
age, por fases, comoclassify, y los de la fuente del release, comosourceFailure. Nada se presenta como válido antes de que acabe el paso 17: la salida se cierra al publicar y se aborta tras cualquier fallo, y el sink se aborta tras cualquier fallo posterior a subegin(§56).
- La
- La firma y el sello quedan para la etapa 5, detrás de un punto de enganche (
lib/src/verdicts.dart): el área desecurityse lee solo hasta donde lo exigen la trama deBODYy el área, y sus veredictos los da unSecurityEvaluator, que recibeSECURITY_CBOR, los bytes del head, el control y el formato, elround_timey las claves de autor, lo que tomannewSecurityContextyEvaluateSecurityInde Go. El de hoy,notEvaluated, no evalúa nada, y uno que falla no impide abrir.OpenOptions.accept, elAcceptde Go, ve los veredictos antes del paso 18 y puede negarse a publicar los ficheros. AgePayloadDecryptor.wipeborra la clave del STREAM de una apertura que acaba antes del final dePAYLOAD_AGE.- Vectores de Go:
tool/mutation_go_texts.go, port descripts/mutation-go-texts.godedatekeys-tssobre eltestdata/de este repositorio, escribemutation_texts.json: el texto decapsule.Openy sus comprobaciones, con su detalle, en los 210 casos del corpus. Go enc531e93y el corpus despec-v0.11coinciden en el código y el paso de todos, y Go en el tagspec-v0.11da el mismo fichero, byte a byte;tool/open_go_vectors.go, en una exportación dedatekeys-goporque usainternal/testkit, escribeopen_cases.json(cada fixture con cada credencial, y 117 aperturas de fixtures editados o con otras opciones, en cada paso que el corpus no alcanza, con los sinks y la salida que fallan y el rechazo deAccept),open_heads.json,open_notes.json,open_inspect.json(el texto decapsule.Inspecten las 5110 mutaciones deinspect_differential.json, donde Go enc531e93y el fichero también coinciden, y la salida de la CLI con notas públicas) yopen_vectors.g.dart, con siete fixtures pequeños y una parte de cada fichero para Node.js. Los casos editados se sellan otra vez con las claves y los nonces de los fixtures, así que la salida es la misma en cada ejecución.
- Pruebas. Los 24 fixtures se abren con cada credencial que documentan, y sus 24
.inspect.jsonsalen byte a byte; los 210 casos del corpus dan el código, el paso, el texto y cada comprobación de Go; los 169 casos deopen_cases.json, también el estado del sink, el contenido o los ficheros y las extensiones inutilizables; todo, en memoria y desde una fuente que se lee a trozos, con el mismo resultado. Cápsulas de varios MiB, hechas desde los fixtures, prueban el streaming: la salida recibe cada chunk al autenticarse, y un chunk posterior que falla la aborta sin cerrarla. Una cápsula con un stanza para una llave de palabras se abre con ella. 518 pruebas nuevas en la VM y 83 en Node.js: 1320 y 291 en total. - Fallos inyectados, uno a uno y revertidos: 15. Las pruebas los detectan todos en la VM: un paso fuera de orden, el
capsule_digestsin comprobar, la salida publicada antes del final del paso 17, el SHA-256 de un fichero sin comparar, el relleno sin comprobar en los formatos 2 y 3, R8 sobre unidades UTF-16, una nota de 1025 bytes aceptada, un fallo deageclasificado en la otra fase, el error de una fuente con su propio código en el paso 9, una identity que abre dos stanzas aceptada, un head inválido informado sin leer hasta el final, el autor comprobado antes que el comentario, el sink sin abortar y el prefijo de la inspección un byte corto. En Node.js, 13: el orden del comentario y del autor y el prefijo solo los ven las pruebas de la VM. tool/open_bench.dartmide la apertura: en la VM, una cápsula pequeña tarda unos 50 ms y 64 MiB en streaming, 1,5 s en el formato 1 y 2,6 s en un fichero del formato 3. Las cifras, en el README.- Un fallo de dart2js de Dart 3.13, ajeno a la librería: un objeto que llega al campo de otro a través de
c ? null : objetopuede perder las escrituras que reciba allí. El README lo explica.
Etapa 4a: rutas, textos y llave de palabras (05-10-2026)
- Las reglas de rutas y de textos (
lib/src/pathrule.dart), port deinternal/pathrulededatekeys-goenc531e93, sobre las tablas de Unicode 18.0.0 y WindowsBestFit que generadatekeys-go(lib/src/pathrule_tables.dart):- NFD con el orden canónico y Hangul, el pliegue de CaseFolding con U+0131 → U+0069, la minúscula simple y
Default_Ignorablecon la lista blanca de R4; - las reglas de una ruta, de R2 a R6c y R10, con las proyecciones de las 15 tablas best-fit; las del árbol, R7 con su clave y las dos rutas que nombra, y R9; y las de los textos del §29.6, el comentario y el autor declarado;
- los textos de error de Go, en
PathRuleException, sin código normativo: la cabecera y la nota lo pondrán en la etapa 4c. Como en Go, cada regla devuelve su violación y solo las funciones públicas lanzan; canonicalTables, el texto depathrule.Canonical: una prueba recalculatablesDigestdesde las listas.
- NFD con el orden canónico y Hangul, el pliegue de CaseFolding con U+0131 → U+0069, la minúscula simple y
- Bytes, como Go. Las funciones acabadas en
Utf8toman los bytes de un string de Go, donde un byte que no es UTF-8 válido es U+FFFD, y las demás unStringcomo lo escribeutf8Bytes. Los límites cuentan bytes, y R6b puntos de código, nunca unidades UTF-16. Así cualquier entrada da el resultado de Go, también el final de R4b tras bytes no válidos, que Go cuenta como U+FFFD de tres bytes. - La llave de palabras del §38.1 (
lib/src/wordkey.dart), port dewordkey:normalizeWords,checkWordscon los textos de Go,wordKey, con el PBKDF2-HMAC-SHA256 de la etapa 2 y 600 000 iteraciones,wordKeyPasswordywordKeySalt, la P y la S del §38.1, ywordIdentity.lib/datekeys.dartexportanormalizeWords,checkWords,wordKey,WordKeyExceptiony sus constantes, como el paquete públicowordkeyde Go;wordIdentityqueda interna, porque devuelve una identity deage.dart. - Vectores de Go en
test/vectors/, con su copia en Dart para Node.js:pathrule_vectors.json, detool/pathrule_go_vectors.go, que corre en una exportación dedatekeys-goporqueinternal/pathruleno se puede importar desde fuera de su árbol: los casos de las pruebas de Go y dedatekeys-ts, los dos lados de cada límite de R2, R3 y R6b, 1300 cadenas y 350 árboles de una semilla fija, también con bytes que no son UTF-8 válido, los casos de R9 y, por plano, el SHA-256 de una línea por punto de código de cada función;wordkey_vectors.json, detool/wordkey_go_vectors.go: 400 textos y sus palabras, 515 listas de palabras con el resultado deChecky cuatro llaves, la primera la del §38.1.
- Pruebas. 31 nuevas en la VM y 18 en Node.js. Integrada encima de la 4b, el total es de 802 pruebas en la VM, sin ninguna aplazada, y 208 en Node.js. Cada punto de código de los 17 planos da el resultado de Go en las siete funciones; los de los planos 0, 1 y 14, también en Node.js. Las llaves de 600 000 iteraciones corren solo en la VM.
- Fallos inyectados, uno a uno y revertidos: 45, en el orden canónico, los ignorables, la lista blanca, el pliegue y la minúscula, las descomposiciones, los límites, las cuentas en UTF-16, las tablas best-fit, Hangul, los bytes no válidos, R4b, R6, R7, R9, R10, los textos, el texto canónico, y en la llave de palabras el separador, los espacios, las marcas, la cuenta de letras, las palabras repetidas y la sal. Las pruebas los detectan todos, cada uno con una prueba que corre también en Node.js. Cuatro se escapaban al principio, uno de R2, uno de R3 en unidades UTF-16, U+036F en las palabras y DEL en
checkWords: los vectores llevan ahora los dos lados de cada límite. tool/pathrule_bench.dartmide las reglas y la llave de palabras: en la VM, una ruta tarda de 9,5 a 28 µs, un comentario de 16 KiB 0,7 ms y la llave 1,1 s. Las cifras, en el README.
Etapa 4b: los formatos de la cápsula y de la llave de acceso (05-10-2026)
- Las tramas (
lib/src/framing.dart): el PRELUDE de un.dkcy la trama de una.dkk, con las comprobaciones de los §23 y §40 en su orden y los textos de Go;splitCapsule, los pasos 1 a 3 de la inspección, con suFramingException; yheaderBinding. El formato de una cápsula es el enumCapsuleFormat. - El relleno (
lib/src/padding.dart):paddedLengthypayloadAgeLengthcon los códigos dePaddingRule, exactos hasta L_MAX también en la web, sin desplazamientos ni máscaras de más de 31 bits; yPaddingCheck, la comprobación del plaintext dePAYLOAD_AGEfrente a L y P del paso 17, por trozos. BODYdel formato 3 (lib/src/body.dart): su trama y el área, sin el head. Y elcapsule_digestincremental (lib/src/digest.dart). Son internos, como endatekeys-ts.- Las extensiones (
lib/src/extension.dart): las reglas de un array, al decodificarlo y antes de escribirlo; los registros, con la comprobación de ladatay los lugares del §72; las extensiones críticas y no críticas de cada objeto; y la regla de los codificadores del §72,checkWrite, conStandardExtensions, a la que se dan las comprobaciones de la nota y del localizador. - El Provider Profile (
lib/src/profile.dart): su CBOR,profile_hash, las reglas 1 a 3 del §12.1 en su orden, el chain hash de drand,maxRoundy el registro con Quicknet pinneado.Profileimplementa elPinnedProfilede la etapa 3. - La DateKey (
lib/src/datekey.dart):dk1_con la aceptación y los textos de Go, también los de su JSON; la resolución de un instante a su ronda y la hora de una ronda; eInstant, al nanosegundo, con el RFC 3339 detime.Parsey deFormatde Go. PUBLIC_HEADER,CONTROL_CBORde las versiones 1 a 3 y la.dkk(lib/src/header.dart,control.dartyaccesskey.dart), en las capas del §69.1.I_PAYLOADyaccess_materialse copian una vez y se borran en todos los caminos.- Los errores que Go devuelve sin código normativo, como un código de relleno que no existe o una extensión fuera de su sitio al escribir, son
ArgumentErrorcon el texto de Go. lib/datekeys.dartexporta los formatos, comoindex.tsdedatekeys-ts.- Vectores de Go (
tool/formats_go_vectors.go, en el contexto del módulo dedatekeys-goenc531e93, sin cambiar nada en él): unos 6 400 casos entest/vectors/formats_*.json, con el resultado, el código y el texto de Go: tramas, cabeceras, controles,.dkk, perfiles, extensiones,dk1_, RFC 3339, rondas, relleno hasta L_MAX, la comprobación del relleno decapsule.Open, los codificadores, los límites del §57,BODY, y cada fallo de una lista, solo y con cada otro, para la precedencia del §69.1.formats_vectors.g.dartlleva uno de cada ocho para Node.js. Con el tagspec-v0.11el generador da la misma salida, salvo la regla de los codificadores del §72, que ese tag no tiene. - Pruebas. Los 24 fixtures y las 6
.dkk;dk1.json,quicknet_rounds.json,profile_quicknet.json,padding.jsony los 172 esquemas decbor.jsonque la etapa 1 dejó aplazados; y el diferencial. 404 pruebas nuevas en la VM y 77 en Node.js: 771 y 190 en total, sin ninguna aplazada. - Fallos inyectados, uno a uno y revertidos: 24. Las pruebas detectan 21, en la VM, en Node.js o en las dos; el de Padmé con desplazamientos de más de 31 bits, solo en Node.js, como debe ser. Dos no se detectaban al principio, y por ellos el generador escribe ahora cada par de fallos de capas distintas y cada bit de
FLAGSyRESERVED: una DateKey comprobada antes que la regla entre los arrays de extensiones, y el bit alto deFLAGSignorado. Los otros tres no cambian ningún resultado: quitar la comprobación de la recodificación dePUBLIC_HEADERo deCONTROL_CBOR, porque sus decodificadores, como los de Go, ya rechazan toda forma no canónica, y leer una longitud con un desplazamiento de 24 bits, porque los operadores de bits compilados a JavaScript dan 32 bits sin signo.
Etapa 3: BLS12-381 y tlock (05-10-2026)
- BLS12-381, de código propio, como lo calcula
kilic/bls12-381v0.1.0 paradrand/kyber-bls12381v0.3.4:- la capa del cuerpo,
Fp, un extension type sobreBigInt, conFpWidepara las sumas de productos sin reducir; la única que toca la representación, para que unos limbs fijos la puedan sustituir sola; - Fp2, Fp6 y Fp12 con las fórmulas de kilic, reduciendo cada coeficiente una vez, el Frobenius con sus coeficientes y el cuadrado ciclotómico;
- G1 y G2, su codificación comprimida y los veredictos de
FromCompressed(§12.2), concheckCompressedPoint, quelib/datekeys.dartexporta comodatekeys-ts. El subgrupo de G2 se comprueba con ψ(P) = ·P; - el emparejamiento ate óptimo con las rectas y la exponenciación final de kilic: GT es su valor, serializado c1 antes que c0 en cada nivel;
- el hash a G1 del RFC 9380 con el DST de Quicknet y de tlock, sumando las salidas del mapa en E′ como kilic.
- la capa del cuerpo,
- El IBE de tlock (
lib/src/ibe.dart),DecryptCCAonG2yEncryptCCAonG2dedrand/kyberv1.3.2 para Quicknet, comoibe.ts: H2 sobre GT en el orden de kilic, H3 con su rechazo de candidatos, H4, la identidad de la ronda y las puertas de la firma y de U, con razones y textos fijos que no llevan ningún valor del cálculo. El cifrado admite un sigma dado, para reproducir byte a byte los vectores de Go; es interno hasta el escritor, la etapa 6. - Releases (
lib/src/release.dart):verifyReleaseesprovider.Verify, en su orden y con sus textos, solo para el scheme de Quicknet, comorelease.ts;suppliedRelease; yfetchRelease, la regla del paso 9: lo que lance una fuente esERR_RELEASE_UNAVAILABLE. Los exportalib/datekeys.dart, conPinnedProfile, lo que lee del perfil, que llega con la etapa 4. - El stanza tlock (
lib/src/tlock.dart):unwrapTlockStanzahace lo queNewTimeIdentityy suUnwrapcon los argumentos y el cuerpo del stanza, ywrapTlockStanzalo queNewTimeRecipient, con los códigos y los textos deagewrap. Es interno: lo usará la apertura de la etapa 4. - Diferencias con Go, a propósito, las de
datekeys-ts: otro scheme que el de Quicknet daERR_UNKNOWN_PROFILEcon un texto propio, y el punto en el infinito nunca es una firma válida, mientras que Go la acepta si la clave pública también es el punto en el infinito, una clave que ningún perfil pinneado tiene. BigIntno es de tiempo constante. Verificar y descifrar solo manejan datos públicos; al cifrar, sigma y r son secretos. El README lo explica.- Vectores de Go en
test/vectors/, que escriben cuatro programas detool/con las librerías de la caché de módulos, en el contexto del módulo dedatekeys-goy sin cambiar nada en él:bls12381_vectors.json: las 157 codificaciones límite dedatekeys-tscon el veredicto de Go, y decodificaciones, sumas, múltiplos, emparejamientos, hashes a G1, el mapa de un elemento y firmas BLS con una semilla fija;ibe_vectors.json, port del generador dedatekeys-tssobre los fixtures detestdata/: GT y H2, H3, H4, identidades de rondas, el stanza de cada fixture con su file key, los ciphertexts de kyber y los veredictos deDecryptCCAonG2;tlock_vectors.json, port también: el cifrado de kyber con sigma fijo y los ciphertexts que escribiódatekeys-tsy abre Go;release_vectors.json: los veredictos, códigos y textos deprovider.Verify,NewTimeIdentityconUnwrapyNewTimeRecipient.
- Pruebas. 71 nuevas en la VM y 30 en Node.js. La etapa se hizo en paralelo con la 2, en otra rama, desde la etapa 1; con las dos integradas hay 367 pruebas en la VM, más una aplazada, y 113 en Node.js. Las que leen los vectores llevan
@TestOn('vm'); en Node.js corren las propiedades de la aritmética y unos pocos vectores de Go copiados en Dart, que una prueba en la VM compara con los JSON. Un emparejamiento tarda allí un cuarto de segundo, así que los bucles sacan menos casos. - Fallos inyectados, uno a uno y revertidos: 25, en la torre, las curvas, el emparejamiento, el hash, el IBE, los releases y el stanza. Las pruebas los detectan todos, en la VM y en Node.js.
tool/bls12381_bench.dartmide BLS12-381 y tlock en la VM y compilado a JavaScript: en la VM, un emparejamiento tarda unos 11 ms, la verificación de la firma de una ronda 16 ms y el descifrado de un stanza 20 ms. Las cifras, en el README.
Etapa 2: primitivas y age (05-10-2026)
- Primitivas, de código propio. Son internas:
lib/datekeys.dartno las exporta.- SHA-256 con su compresión, y HMAC-SHA256 con los estados interior y exterior de la clave calculados una vez. Sobre ellos, HKDF-SHA256 (RFC 5869) y PBKDF2-HMAC-SHA256 (RFC 8018), cuyas iteraciones son dos compresiones sobre palabras: 600 000 iteraciones tardan 1,1 s en la VM, frente a unos 3,5 s con el HMAC de
package:crypto. - scrypt (RFC 7914) con Salsa20/8, con las comprobaciones y los textos de
scrypt.Keyde Go. - ChaCha20, Poly1305 en diez limbs de 13 bits, y ChaCha20-Poly1305 (RFC 8439), con el tag comparado en tiempo constante.
- X25519 (RFC 7748) sobre el cuerpo de TweetNaCl en doubles, con clamping y el secreto todo a ceros rechazado con el texto de
crypto/ecdh. - Ed25519 estricto (§29.9), port de
internal/ed25519strict:verifyStrict,canonical,smallOrderyonCurve. La aritmética de los puntos es la de TweetNaCl; los escalares módulo ℓ yonCurveusanBigInt, que no es de tiempo constante, solo con datos públicos. - El Base64 de Go, con su modo estricto y el offset de sus errores, y el Bech32 de
age, con sus textos. - De
package:cryptosolo se usa SHA-512, en Ed25519.
- SHA-256 con su compresión, y HMAC-SHA256 con los estados interior y exterior de la clave calculados una vez. Sobre ellos, HKDF-SHA256 (RFC 5869) y PBKDF2-HMAC-SHA256 (RFC 8018), cuyas iteraciones son dos compresiones sobre palabras: 600 000 iteraciones tardan 1,1 s en la VM, frente a unos 3,5 s con el HMAC de
- Lectura de
agev1, port defilippo.io/agev1.3.2 (lib/src/age.dart):- la cabecera y sus límites de
internal/format, con los textos de error de Go; - el MAC de la cabecera, sobre la cabecera escrita otra vez como la escribe
age; - los stanzas X25519 y scrypt. La identity de scrypt rechaza por defecto un factor de trabajo por encima de 16, como
authorkeyde Go; el deagees 22; - la clave del payload y el STREAM de
internal/stream, que se descifra según llega el texto cifrado, con los mismos casos de fin de fichero que el lector de Go: sin chunk final, chunk que no se autentica, último chunk vacío y datos tras el final; - cada fallo es una
AgeExceptioncon el texto de Go y su fase, la cabecera o el payload, para que la etapa 4 dé las razones fijas decapsule.classify. LaDateKeysExceptionde una identity pasa sin cambios.
- la cabecera y sus límites de
agewrap(lib/src/agewrap.dart): las reglas de stanzas deOUTER_TIME_AGE,PAYLOAD_AGEeINNER_ACCESS_AGE, la lectura de los stanzas sin secretos,PayloadIdentityyAccessIdentity, con los textos y los códigos de Go.checkTimeStanzasrecibe la ronda, el chain hash y el id del perfil, que llegará con la etapa 4.- Vectores de Go en
test/vectors/, que escribentool/gen_primitive_vectors.goytool/gen_age_vectors.gocon las librerías de la caché de módulos, en el contexto del módulo dedatekeys-goy sin cambiar nada en él:primitives.jsony su copia en Dart,primitives.g.dart: RFC 5869, 7748, 7914, 8032 (porsign.inputde Go) y 8439, PBKDF2 con el vector del §38.1, los puntos de orden pequeño, Base64 y Bech32. Incluye dos mensajes con los que una suma de productos de Poly1305 pasa de 2^32: un acarreo tomado con un desplazamiento fallaría en la web, y una prueba lo detecta en Node;age.json: ficheros X25519 y scrypt, sus cortes y manipulaciones, un corpus de cabeceras contra la gramática del §28.1 y el límite de 2 MiB, y las reglas e identities deagewrap, con el texto de Go en cada caso;age_fixtures.json: elPAYLOAD_AGEde los 24 fixtures con supayload_identity, y elINNER_ACCESS_AGEde los 6time_and_key, sacado deOUTER_TIME_AGEcon el release del fixture.
- Pruebas. 296 en la VM y una aplazada, 127 más que en la etapa 1; 83 en Node.js, 27 más. Las de las primitivas y las de
agesin ficheros corren también compiladas a JavaScript, sin los casos largos (PBKDF2 a 600 000 iteraciones, scrypt con logN 16, X25519 iterada 1000 veces). - Fallos inyectados, uno a uno y revertidos: 46 en las primitivas,
ageyagewrap. Las pruebas detectan todos los que cambian un resultado. Cuatro no lo cambian: la máscara de una mitad de rotación de SHA-256 cuyos bits altos solo llegan a sumas enmascaradas, el redondeo de_carcuando sus entradas nunca son negativas, el de un acarreo del producto del cuerpo, que conserva el valor, y el acarreo del limb 1 de Poly1305 tomado con desplazamiento, cuya suma no pasa de 2^32. tool/bench.dartmide las primitivas en la VM y compilado a JavaScript; las cifras, en el README.
El gate con Node.js (05-10-2026)
tool/check.shcorre tambiéndart test -p node: las pruebas que no leen ficheros, compiladas a JavaScript, comprueban en cada commit que los enteros son exactos en la web. Lo decidió el autor.
Etapa 1: errores, bytes, CBOR y DER (05-10-2026)
- Errores normativos (§69).
ErrorCodecon los 19 códigos en el orden del spec yDateKeysException, con el mensajecontexto: CÓDIGOde Go.wrapywithContexthacen lo quefmt.Errorf("prefijo: %w"), yerrorCodelo quedatekeys.Code. Una prueba compara el catálogo con las líneasERR_del §69, leído dedatekeys-goen el tagspec-v0.11. - Bytes. Hexadecimal, comparación y concatenación.
- UTF-8 estricto: rechaza lo que rechaza
utf8.Validde Go y conserva un U+FEFF inicial. ElUtf8Decoderdedart:convertlo descarta, en la VM y en la web, así que no se usa. - Un
Stringcon un surrogate suelto se escribe en UTF-8 generalizado (WTF-8), que no es UTF-8 válido. utf8.DecodeRuney el%qde Go (strconv.Quote). La tabla destrconv.IsPrintde Go 1.26 se copia dedatekeys-ts, y una prueba fija el SHA-256 del conjunto de runas.
- UTF-8 estricto: rechaza lo que rechaza
- CBOR (§58, §58.1). Port de
codec/codec.go:CborEncoder,CborDecoder,unmarshalCbor,peekSchema,checkSchemaywalkCbor, con los mismos textos de error.- Los enteros son exactos en la VM y en la web: un
inthasta 2^53-1 y unBigIntpor encima, también en las claves de los mapas y en los textos de error. - El
Encoderde Go rechaza el UTF-8 inválido; aquí el único texto inválido es un surrogate suelto, y el error lo cita como Go citaría sus bytes en UTF-8 generalizado. CborEncoder.uintexige 0..2^53-1, con un texto propio, como endatekeys-ts;uint64escribe hasta 2^64-1.
- Los enteros son exactos en la VM y en la web: un
- DER estricto. Port de
internal/der/der.goen601e6d2, ya del borrador v0.12:check,split,content,setOfSortedyparseTime.parseTimedevuelve unDerTimecon los campos y los segundos Unix, exacto al nanosegundo. Es interno:lib/datekeys.dartno lo exporta. - Pruebas. Port de
errors_test.go,codec_test.go,internal_test.go,vectors_test.goyder_test.go, decbor.test.tsy debytes.test.ts, con los textos de error que imprime Go.- Los objetivos de fuzzing de Go son propiedades con semilla, contra un codificador y un decodificador de referencia escritos aparte, como
internal/cbortest. testdata/vectors/cbor.json: los 36accepty los 67reject, con los límites dewalky los valores. Los 172 vectores deschemasse leen y se aplazan: necesitan los decodificadores de esquema de la etapa 4.- 169 pruebas en la VM y una aplazada. Las 56 que no leen ficheros pasan también en Node.js (
dart test -p node); las que leen ficheros llevan@TestOn('vm').
- Los objetivos de fuzzing de Go son propiedades con semilla, contra un codificador y un decodificador de referencia escritos aparte, como
Etapa 0: el paquete (05-10-2026)
- Paquete
datekeysen Dart puro, sin Flutter, para Dart 3.13 y sin publicar (publish_to: none). testdata/sincronizado con el tagspec-v0.11dedatekeys-go(ae33434): 124 ficheros. Sutestdata/SOURCE.jsones el mismo, byte a byte, que escribedatekeys-tspara ese commit.tool/sync_testdata.dart, el equivalente descripts/sync-testdata.mjs.test/testdata_test.dartcomprueba la copia y que cada fichero nombre la versión de la especificación.tool/check.sh, el gate local.- Dependencias:
package:cryptoen ejecución ypackage:testen desarrollo. - Licencia Apache-2.0.