Skeleton for the TypeScript implementation of DateKeys v0.8.x: the plans in docs/, testdata/ vendored from g.activething.com/go/DateKeys at 5719f6a with a zero-dependency sync and check script, and the TypeScript and vitest tooling already used by the prototype, which now lives in ../AppOld. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>main
commit
ba8418cdbf
@ -0,0 +1,6 @@
|
||||
# Source files are normalised to LF.
|
||||
* text=auto eol=lf
|
||||
|
||||
# Vendored Go fixtures and vectors must keep their exact bytes: their SHA-256 is
|
||||
# recorded in testdata/SOURCE.json and in the fixtures themselves.
|
||||
testdata/** -text
|
||||
@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
.svelte-kit/
|
||||
build/
|
||||
coverage/
|
||||
*.log
|
||||
@ -0,0 +1,50 @@
|
||||
# DateKeys App
|
||||
|
||||
Implementación en TypeScript del protocolo DateKeys v0.8.1 y página de prueba en el navegador. Sustituye al prototipo, archivado en `../AppOld` (API Quicknet en Go, CLI tlock y cliente Svelte, commit `4d2b0a1`).
|
||||
|
||||
La implementación de referencia es la librería Go `g.activething.com/go/DateKeys`, en `../datekeys-go`. El plan de trabajo está en [docs/PLAN_codec_cbor_y_pagina_svelte.md](docs/PLAN_codec_cbor_y_pagina_svelte.md).
|
||||
|
||||
## Contenido previsto
|
||||
|
||||
| Parte | Ubicación | Paso del plan |
|
||||
|---|---|---|
|
||||
| Codec CBOR del subconjunto, parsers de schema, DateKey, `inspect` | `src/lib/dkc/` | 4 |
|
||||
| Página inspector, sin red | SvelteKit, ruta `/inspect` | 5 |
|
||||
| Cifrado y descifrado en el navegador | fase 2 | 6 |
|
||||
|
||||
## Reglas
|
||||
|
||||
- Los fixtures y vectores del Go son la verdad. Este proyecto nunca genera fixtures propios: `testdata/` es una copia exacta de un commit de la librería Go.
|
||||
- Dependencias de ejecución: solo `age`, `drand`, `tlock` y lo que ellas arrastran. Ahora mismo no hay ninguna.
|
||||
- Tooling de desarrollo: solo el de la lista siguiente. Cualquier otra dependencia se propone por escrito y no se instala sin aprobación.
|
||||
|
||||
## Tooling de desarrollo
|
||||
|
||||
| Paquete | Versión | Estado |
|
||||
|---|---|---|
|
||||
| `typescript` | 5.9.3 | en `package.json`; ya usado en el prototipo |
|
||||
| `vitest` | 5.0.1 | en `package.json`; ya usado en el prototipo |
|
||||
| `@vitest/coverage-v8` | la de `vitest` | pendiente de aprobación; cobertura del 100 % del codec |
|
||||
| `@types/node` | 24.x | pendiente de aprobación; tests que leen `testdata/` desde disco |
|
||||
| `svelte`, `@sveltejs/kit`, `@sveltejs/vite-plugin-svelte`, `vite`, `svelte-check` | las del prototipo | paso 5 |
|
||||
| `@sveltejs/adapter-static` | por fijar | paso 5, pendiente de aprobación: la página es estática y no necesita servidor |
|
||||
|
||||
Todas las versiones se fijan exactas y `package-lock.json` se versiona.
|
||||
|
||||
## testdata
|
||||
|
||||
```bash
|
||||
npm run testdata:sync
|
||||
```
|
||||
|
||||
Copia `testdata/` de `../datekeys-go` en `HEAD`, leyendo los blobs con git para no arrastrar cambios sin commit, y escribe `testdata/SOURCE.json` con el commit completo y el SHA-256 de cada fichero. Para fijar otro commit: `node scripts/sync-testdata.mjs sync --commit <rev>`.
|
||||
|
||||
```bash
|
||||
npm run testdata:check
|
||||
```
|
||||
|
||||
Comprueba que los ficheros coinciden con `SOURCE.json`, sin faltantes ni sobrantes, y vuelve a leerlos del repositorio Go en el commit registrado. Sin la opción `--against`, `node scripts/sync-testdata.mjs check` verifica solo la copia local. Ambos comandos usan solo Node y git.
|
||||
|
||||
`.gitattributes` marca `testdata/**` como binario para que git no altere ningún byte.
|
||||
|
||||
Copia actual: 22 ficheros de `5719f6a4b3654f608091e32290d9fcfbc7e38062`.
|
||||
@ -0,0 +1,276 @@
|
||||
# Plan de implementación: `datekeys-go`
|
||||
|
||||
Implementación de referencia en Go de la **DateKeys Protocol Specification v0.8.1**.
|
||||
|
||||
Estado: borrador para revisión. Fecha: 25 de septiembre de 2026.
|
||||
|
||||
---
|
||||
|
||||
## 1. Objetivo y principios
|
||||
|
||||
Construir desde cero una librería Go que implemente el protocolo base tal como lo fija la v0.8.1, con estas reglas:
|
||||
|
||||
1. **La especificación manda.** El código no añade semántica. Si el código descubre un problema en el spec, se abre un caso reproducible según la política de cambios de la sección 76.
|
||||
2. **Cero criptografía propia.** Solo `age`, `tlock` y la verificación BLS de `drand`. La librería aporta framing, CBOR, bindings, reglas de verificación y flujo.
|
||||
3. **Validar todo lo verificable localmente antes de tocar red o secretos.** Sección 63.
|
||||
4. **Fallar cerrado.** Ningún plaintext parcial, ningún error enmascarado, ningún "verified: true" ajeno.
|
||||
5. **Superficie pequeña y auditable.** Pocas dependencias, todas pinneadas, builds reproducibles, y un mapa de trazabilidad spec ↔ código para la revisión externa.
|
||||
6. **Nueva librería, no refactor.** Lo aprovechable del prototipo se copia y se adapta; el repositorio actual no se modifica.
|
||||
|
||||
---
|
||||
|
||||
## 2. Alcance de la primera versión de la librería (v0.1.0)
|
||||
|
||||
Implementa:
|
||||
|
||||
- DateKey: resolución fecha → ronda, `dk1_` canónico, vectores (secciones 14 a 19).
|
||||
- Provider Profile Quicknet: struct, CBOR canónico, `profile_hash`, registro pinneado (secciones 10 a 13).
|
||||
- Adaptador drand: obtención de releases por relays, verificación BLS local, modo estricto (secciones 35, 45 a 52).
|
||||
- `.dkc`: prelude, PUBLIC_HEADER, CONTROL_CBOR, `header_binding`, `time_only` y `time_and_key`, flujo de cifrado y descifrado completo con SHOULD y MUST (secciones 20 a 39, 61 a 63).
|
||||
- `.dkk`: framing, body, identity portable de un solo uso (secciones 40 a 44).
|
||||
- Extensiones genéricas con reglas de duplicados y orden canónico (sección 54).
|
||||
- Catálogo de errores (sección 69). Límites de parser (57). Canonicidad CBOR y omisión de opcionales (58, 58.1).
|
||||
- Fixtures, mutaciones, fuzzing y CLI mínima.
|
||||
|
||||
No implementa, a propósito:
|
||||
|
||||
- Servidor de Release API ni Release Queue. La librería define la interfaz de fuente de releases; el servidor actual podrá consumirla más adelante.
|
||||
- Almacenamiento, entrega de `.dkk`, servicios, extensiones concretas.
|
||||
- Cliente TypeScript. Vendrá después con la misma suite de fixtures.
|
||||
|
||||
---
|
||||
|
||||
## 3. Decisiones de diseño
|
||||
|
||||
### 3.1 Módulo y layout
|
||||
|
||||
Repositorio: Gitea propio, `https://g.activething.com/go/DateKeys.git`. Módulo: `g.activething.com/go/DateKeys`, la ruta que el propio Gitea anuncia en su etiqueta `go-import`. Si más adelante se quiere una ruta en `datekeys.com` que no dependa del servidor, es un cambio de una línea en `go.mod` y un `sed` en las importaciones, siempre antes de publicar. Go 1.26, con `go.mod` fijando la última versión de parche de la serie. No se usa GitHub para nada del proyecto; las dependencias alojadas allí se obtienen como módulos Go, igual que cualquier otra.
|
||||
|
||||
```text
|
||||
datekeys-go/
|
||||
go.mod
|
||||
LICENSE Apache-2.0 (código)
|
||||
README.md SECURITY.md CONTRIBUTING.md TRADEMARKS.md CHANGELOG.md
|
||||
spec/
|
||||
DateKeys_Protocol_Specification_v0.8.1.md copia congelada
|
||||
datekeys.cddl schema normativo de todos los CBOR
|
||||
datekey/ DateKey, resolución, dk1_ canónico
|
||||
profile/ Provider Profile, CBOR canónico, hash, registro pinneado, quicknet.go
|
||||
provider/ interfaces Condition, Release, ReleaseSource, Verify
|
||||
provider/drand/ relays HTTP, verificación BLS, modo estricto
|
||||
codec/ CBOR determinista: Encode, Decode con comprobación por reencodificación, límites
|
||||
codec/bech32/ codificación Bech32 para la forma humana de identities (ver 4.3)
|
||||
agewrap/ identities y recipients envolventes de age: cardinalidad, sonda de inspección, stanza tlock
|
||||
extension/ tipos y reglas de extensiones
|
||||
capsule/ .dkc: framing, cabecera, control, binding, Encrypt, Inspect, Open
|
||||
accesskey/ .dkk: framing, body, identity
|
||||
errors.go sentinel errors del catálogo de la sección 69
|
||||
internal/testkit/ generación de fixtures, escritor de cabeceras age malformadas, mutadores
|
||||
testdata/fixtures/ fixtures oficiales y sus valores esperados
|
||||
docs/traceability.md mapa sección del spec → paquete, función, test
|
||||
cmd/datekeys/ CLI: encrypt, decrypt, inspect, datekey, profile
|
||||
```
|
||||
|
||||
### 3.2 API pública, deliberadamente pequeña
|
||||
|
||||
```go
|
||||
// datekey
|
||||
type DateKey struct { ProfileID string; Round uint64 }
|
||||
func Resolve(p *profile.Profile, at time.Time) (DateKey, error) // §15, precisión completa
|
||||
func Parse(s string) (DateKey, error) // §19, solo forma canónica
|
||||
func (d DateKey) Compact() string // dk1_...
|
||||
func (d DateKey) UnlockAt(p *profile.Profile) time.Time
|
||||
|
||||
// profile
|
||||
type Profile struct { ID, Provider, Network string; ChainHash [32]byte; PublicKey []byte;
|
||||
Period time.Duration; GenesisTime int64; Scheme string; GenesisSeed [32]byte }
|
||||
func (p *Profile) CanonicalCBOR() []byte
|
||||
func (p *Profile) Hash() [32]byte // §11
|
||||
var Quicknet *Profile // §12, pinneado en el binario
|
||||
type Registry interface { Lookup(id string) (*Profile, bool) }
|
||||
|
||||
// provider
|
||||
type Condition struct { Round uint64 }
|
||||
type Release struct { Round uint64; Signature []byte }
|
||||
type ReleaseSource interface { Fetch(ctx context.Context, p *profile.Profile, c Condition) (Release, error) }
|
||||
func Verify(p *profile.Profile, c Condition, r Release) error // §51, BLS local siempre
|
||||
|
||||
// capsule
|
||||
type Policy uint8 // TimeOnly = 0, TimeAndKey = 1
|
||||
type EncryptOptions struct {
|
||||
Profile *profile.Profile
|
||||
UnlockAt time.Time
|
||||
Policy Policy
|
||||
Recipients []age.Recipient // X25519 de destinatarios conocidos
|
||||
NewPortableKey bool // genera I_ACCESS y devuelve la .dkk
|
||||
Critical, Noncritical []extension.Extension
|
||||
}
|
||||
type Result struct { DateKey datekey.DateKey; CapsuleID [16]byte; PortableKey *accesskey.AccessKey }
|
||||
func Encrypt(dst io.Writer, src io.Reader, opts EncryptOptions) (*Result, error)
|
||||
|
||||
type Inspection struct { /* prelude, cabecera, política, ronda, unlock, resultado de cada comprobación */ }
|
||||
func Inspect(r io.ReadSeeker, reg profile.Registry) (*Inspection, error) // pasos 1 a 8, sin red ni secretos
|
||||
|
||||
type OpenOptions struct {
|
||||
Registry profile.Registry
|
||||
Source provider.ReleaseSource
|
||||
Identities []age.Identity // X25519 propias del destinatario
|
||||
AccessKey *accesskey.AccessKey // .dkk portable
|
||||
Now func() time.Time // inyectable para tests
|
||||
}
|
||||
func Open(dst io.Writer, r io.ReadSeeker, opts OpenOptions) error // pasos 9 a 18
|
||||
|
||||
// accesskey
|
||||
type AccessKey struct { CredentialID, CapsuleID [16]byte; Type string; Material []byte;
|
||||
Verification *Verification; Critical, Noncritical []extension.Extension }
|
||||
func Encode(w io.Writer, k *AccessKey) error
|
||||
func Decode(r io.Reader) (*AccessKey, error)
|
||||
func (k *AccessKey) Identity() (age.Identity, error)
|
||||
```
|
||||
|
||||
Reglas de la API:
|
||||
|
||||
- `io.Reader` y `io.Writer` en todo; el payload nunca se carga entero en memoria.
|
||||
- `Open` recibe `io.ReadSeeker` para poder inspeccionar la cabecera de PAYLOAD_AGE en el offset `16 + PUBLIC_HEADER_LEN + SEALED_CONTROL_LEN` antes de pedir el release. Si el lector no permite seek, se omite el SHOULD y se aplica el MUST al abrir.
|
||||
- El reloj se inyecta. Ningún paquete llama a `time.Now` directamente salvo la CLI.
|
||||
- Ningún tipo imprime secretos en `String()`. Las identities se pasan como tipos de `age`, no como bytes sueltos, salvo en `accesskey`.
|
||||
|
||||
### 3.3 Cómo se usan `age` y `tlock`
|
||||
|
||||
- **PAYLOAD_AGE e INNER_ACCESS_AGE:** `age.Encrypt` y `age.Decrypt` con recipients e identities X25519 de la API pública de `age`.
|
||||
- **OUTER_TIME_AGE:** `age.Encrypt` con un recipient propio en `agewrap` que llama a `tlock.TimeLock` y `tlock.CiphertextToBytes`, ambos exportados, y emite el stanza `tlock <ronda> <chainhash>` con el mismo formato que `tlock`. Para abrir, una identity propia que comprueba el conjunto completo de stanzas, la ronda y el chain hash contra la DateKey y el perfil, y llama a `tlock.TimeUnlock`, que verifica el beacon antes de descifrar.
|
||||
- Motivo: `tlock.New(...).Decrypt` usa una identity no exportada en la que no podemos imponer la cardinalidad ni evitar que todo error se convierta en `ErrTooEarly`. Usando su núcleo exportado se mantiene la compatibilidad de stanza con `tle` y se gana control del flujo.
|
||||
- **Cardinalidad MUST:** en las tres identities envolventes, dentro de `Unwrap`, que recibe todos los stanzas del fichero. Sin APIs internas.
|
||||
- **Inspección SHOULD:** `age.ExtractHeader` lee únicamente la cabecera del fichero, y `age.DecryptHeader` con una identity sonda, cuyo `Unwrap` registra los stanzas recibidos y devuelve un error centinela distinto de `ErrIncorrectIdentity`, entrega los stanzas parseados por `age` sin descifrar nada ni usar secretos. Ambas funciones son API pública de `age` 1.3. Para PAYLOAD_AGE se aplica en el offset del prelude. No hace falta parser propio.
|
||||
|
||||
---
|
||||
|
||||
## 4. Dependencias
|
||||
|
||||
| Dependencia | Versión | Uso | Nota |
|
||||
|---|---|---|---|
|
||||
| `filippo.io/age` | v1.3.2 | ficheros age, X25519, `Stanza`, `Recipient`, `Identity` | única implementación de STREAM; no se reimplementa |
|
||||
| `github.com/drand/tlock` | v1.2.0 | `TimeLock`, `TimeUnlock`, `CiphertextToBytes`, `BytesToCiphertext` | no se usa `tlock.New`, `Encrypt` ni `Decrypt` |
|
||||
| `github.com/drand/drand/v2` | v2.1.7 | `crypto.Scheme`, `VerifyBeacon`, `chain.Info` | arrastra gRPC y otros; `govulncheck` ya confirmó que no son alcanzables |
|
||||
| `github.com/drand/kyber` y `kyber-bls12381` | transitivas | pairing BLS12-381 | sobre `kilic/bls12-381`, archivado; vigilar y documentar en SECURITY.md |
|
||||
| `github.com/fxamacker/cbor/v2` | v2.9.4 | CBOR determinista | `CoreDetEncOptions` para codificar; límites en decodificación |
|
||||
|
||||
Sin otras dependencias en la librería. Tests con la biblioteca estándar. CLI con `flag`. HTTP con `net/http`.
|
||||
|
||||
### 4.1 Política de versiones
|
||||
|
||||
`go.mod` pinneado, `go.sum` verificado, `go mod verify` en CI, `govulncheck` en cada PR, Renovate (compatible con Gitea) solo para parches, con revisión manual de cualquier cambio en `age`, `tlock`, `drand` o `kyber`.
|
||||
|
||||
### 4.2 Canonicidad CBOR
|
||||
|
||||
Codificar con Core Deterministic Encoding. Al decodificar, además de los límites de `fxamacker` (sin longitudes indefinidas, sin tags, sin claves duplicadas, profundidad y tamaños acotados), se **reencodifica y se compara byte a byte** con la entrada. Cualquier diferencia es `ErrNonCanonicalCBOR`. Es el mismo principio que `dk1_` y no depende de que una librería prometa rechazar todas las formas no canónicas.
|
||||
|
||||
### 4.3 Bech32
|
||||
|
||||
`age` solo acepta identities y recipients X25519 en Bech32 por su API pública, y su paquete Bech32 es `internal`. El `.dkk` guarda 32 bytes crudos por spec. Se necesita un codificador Bech32 de unas cien líneas para pasar de bytes a `AGE-SECRET-KEY-1...` y de `age1...` a bytes. Es codificación, no criptografía. Opciones: copiar el paquete interno de `age` con su licencia BSD y atribución, o reimplementar BIP-173 con vectores de prueba. Propuesta: copiar, para no divergir.
|
||||
|
||||
---
|
||||
|
||||
## 5. Qué se copia del prototipo
|
||||
|
||||
| Origen | Destino | Cambios |
|
||||
|---|---|---|
|
||||
| `pkg/datekeys/key.go`: `RoundForTime`, `TimeForRound`, `ParseDateKey`, `FromRound` | `datekey/` | quitar campos de API y el alias `EncryptionKey`; el struct de respuesta HTTP se queda en el servidor |
|
||||
| `pkg/datekeys/key_test.go`, `FuzzRecipient` | `datekey/` | renombrar, ampliar con vectores de la sección 65 |
|
||||
| `internal/quicknet/trust.go` | `profile/quicknet.go` | añadir CBOR canónico y hash; conservar la autocomprobación de chain hash |
|
||||
| `internal/drand/client.go` y tests | `provider/drand/` | carrera entre relays, respuestas malformadas, cancelación; adaptar a `ReleaseSource` |
|
||||
| `pkg/datekeys/crypto.go` | no se copia | sustituido por `agewrap` sobre el núcleo exportado de `tlock`; se conserva la doble verificación BLS |
|
||||
| `tests/integration/quicknet_test.go` | `provider/drand/`, build tag `integration` | prueba en vivo contra Quicknet |
|
||||
| `cmd/datekeys` | reescrito | misma ergonomía, nueva API |
|
||||
| `internal/api`, `web-client` | no se tocan | consumirán la librería más adelante |
|
||||
|
||||
---
|
||||
|
||||
## 6. Estilo de código
|
||||
|
||||
- Identificadores, comentarios de código, mensajes de error y commits en **inglés**. Documentación de usuario en inglés con versión en español. Motivo: la revisión externa y la comunidad de `age` y drand.
|
||||
- `gofmt`, `goimports`, `go vet`, `staticcheck`, `golangci-lint` con configuración corta: `errcheck`, `govet`, `staticcheck`, `ineffassign`, `unparam`, `gosec` en modo aviso.
|
||||
- Errores: un sentinel por entrada del catálogo de la sección 69, envueltos con `%w` y contexto; nunca se sustituye un error por otro más cómodo. Los tests de mutación comprueban `errors.Is`.
|
||||
- Parsers: límites antes de reservar memoria, `io.LimitReader`, ningún `panic` alcanzable desde la entrada. Todo parser tiene un objetivo de fuzzing.
|
||||
- Sin estado global mutable. El registro de perfiles se pasa explícitamente; el registro por defecto contiene solo Quicknet.
|
||||
- Comentarios de paquete y de función citan la sección del spec que implementan, por ejemplo `// Spec §26`.
|
||||
- Secretos: no se registran en logs, no aparecen en `String()`, se borran de buffers propios al terminar, sin prometer más de lo que Go garantiza.
|
||||
- Salida atómica en la CLI: fichero temporal en el mismo directorio y renombrado al final; nunca sobrescribir destinos existentes.
|
||||
- Versionado semántico. `v0.x` hasta que el spec sea v1.0. Sin promesa de estabilidad de API antes de `v1.0.0`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Estrategia de tests
|
||||
|
||||
1. **Unitarios** por paquete, tabla-driven, biblioteca estándar.
|
||||
2. **Vectores de spec**, generados por el código y guardados como golden files versionados: ronda (frontera, un segundo antes y después, fracción de segundo tras frontera, 2030-01-01, cercanos al genesis), `dk1_` (objeto, JSON canónico, base64url, cadena), `profile_hash` de Quicknet.
|
||||
3. **Fixtures `.dkc` y `.dkk`** (secciones 67 y 68): generados una vez con `internal/testkit` sobre una **ronda ya publicada**, con la firma BLS embebida en el fixture. Los tests verifican la firma contra la clave pinneada y descifran **sin red**. Se comparan todos los valores intermedios: cabecera, prelude, binding, control, `I_PAYLOAD`, plaintext y el error esperado en cada etapa.
|
||||
4. **Corpus de mutaciones** (sección 64): las veinte mutaciones como funciones sobre un fixture válido; cada una afirma su sentinel error concreto.
|
||||
5. **Escritor de cabeceras age malformadas** en `testkit`: añade stanzas o cambia tipos manteniendo el MAC válido, porque en el test se conoce la file key. Demuestra que la comprobación estructural rechaza lo que el MAC aceptaría. Es la prueba directa de la amenaza del creador. Implementación: según la especificación C2SP, el MAC de cabecera es HMAC-SHA-256 con clave HKDF-SHA-256 de la file key e info `header`, sobre el texto de la cabecera; `testkit` lo recalcula con `golang.org/x/crypto/hkdf`, ya presente en el grafo por `age`. Para abrir ficheros de test con una file key conocida se usa `age.NewInjectedFileKeyIdentity`, también API pública.
|
||||
6. **Propiedades:** `Encode(Decode(x)) == x` para todo fixture; toda reordenación de claves o ensanchamiento de enteros se rechaza como no canónico; `Parse(Compact(d)) == d`.
|
||||
7. **Fuzzing** nativo de Go: prelude, PUBLIC_HEADER, CONTROL_CBOR, `.dkk`, `dk1_`, cabecera age vía sonda. Corpus sembrado con fixtures y mutaciones. Presupuesto corto en cada PR, largo en nightly.
|
||||
8. **Integración en vivo** con build tag: cifrar hacia ahora más treinta segundos, esperar, abrir por relays reales. Nightly, no en cada PR.
|
||||
9. **Interoperabilidad con herramientas ajenas:** extraer SEALED_CONTROL de un fixture `time_only` y abrirlo con la CLI `tle` oficial; extraer PAYLOAD_AGE y abrirlo con la CLI `age` usando `I_PAYLOAD`. Si ambas pasan, la afirmación "son ficheros age estándar" queda demostrada por terceros.
|
||||
10. **Cobertura:** mínimo 90 % en `codec`, `capsule`, `accesskey`, `datekey`, `agewrap`; `-race` siempre.
|
||||
11. **CDDL:** `spec/datekeys.cddl` valida los fixtures en un job opcional de CI con la herramienta `cddl`; si no se quiere otra toolchain, se mantiene como documento normativo y se comprueba a mano en cada cambio de schema.
|
||||
|
||||
---
|
||||
|
||||
## 8. CI, releases y cadena de suministro
|
||||
|
||||
- Gitea Actions, con la misma sintaxis que GitHub Actions: `test` en Linux y Windows con la versión estable de Go y la anterior, macOS cuando haya runner; `lint`; `vuln` con `govulncheck`; `fuzz-short`; `integration` nightly por cron; `sbom` con `cyclonedx-gomod`.
|
||||
- Releases con `goreleaser`, que publica en Gitea: `-trimpath`, `CGO_ENABLED=0`, checksums publicados, firma con `cosign` con clave propia, ya que la firma sin clave depende de un proveedor OIDC que Gitea no ofrece.
|
||||
- `SECURITY.md` con canal de reporte, versiones soportadas y la dependencia de `kilic/bls12-381` declarada como riesgo conocido y vigilado.
|
||||
- `TRADEMARKS.md`: "DateKeys" reservado; "implements the DateKey protocol" permitido.
|
||||
|
||||
---
|
||||
|
||||
## 9. Hitos y criterios de aceptación
|
||||
|
||||
**M0. Cimientos.** Esqueleto, `go.mod` pinneado, CI con lint y `govulncheck`, `codec` con canonicidad por reencodificación y límites, `spec/datekeys.cddl` inicial, `profile` con Quicknet, su CBOR y su hash como primer vector oficial.
|
||||
Hecho cuando: el hash del perfil está congelado en un golden file y CI está verde en tres sistemas.
|
||||
|
||||
**M1. Objetos.** `datekey` copiado y ampliado; framing DKC1 y DKK1; codecs de PUBLIC_HEADER, CONTROL_CBOR y `.dkk`; `header_binding`; `extension`; catálogo de errores; fuzzing de todos los parsers.
|
||||
Hecho cuando: los vectores de ronda y `dk1_` están generados y versionados y el fuzzing lleva una noche sin fallos.
|
||||
|
||||
**M2. `time_only`.** `agewrap` con recipient e identity tlock estrictos, sonda de inspección, PAYLOAD_AGE, `Encrypt`, `Inspect`, `Open` con los 18 pasos; `provider/drand` copiado.
|
||||
Hecho cuando: un fixture `time_only` sobre ronda pasada se abre offline, la prueba con `tle` y `age` externas pasa, y las mutaciones aplicables fallan con su error exacto.
|
||||
|
||||
**M3. `time_and_key`.** INNER_ACCESS_AGE con varios recipients, `.dkk` portable de un solo uso, `Identities` y `AccessKey` en `Open`.
|
||||
Hecho cuando: fixtures `time_and_key` con uno y varios recipients se abren offline, la reutilización de `I_ACCESS` está prohibida en `Encrypt` y probada, y las veinte mutaciones están cubiertas.
|
||||
|
||||
**M4. Conformidad.** Suite de fixtures oficial, golden files, `docs/traceability.md` completo, integración nightly, SBOM y release firmada `v0.1.0`.
|
||||
Hecho cuando: cada sección normativa de la 14 a la 69 apunta a al menos un test, y `v0.1.0` se construye de forma reproducible.
|
||||
|
||||
**M5. CLI.** `datekeys encrypt`, `decrypt`, `inspect`, `datekey resolve`, `profile hash`. `inspect` ejecuta solo los pasos 1 a 8 y nunca pide un release ni usa secretos.
|
||||
Hecho cuando: la CLI abre los fixtures y reproduce la ergonomía del prototipo con salida atómica.
|
||||
|
||||
Fuera de estos hitos: SDK TypeScript, migración del cliente web, servidor de Release API sobre la librería, segundo perfil `evmnet`.
|
||||
|
||||
---
|
||||
|
||||
## 10. Trazabilidad
|
||||
|
||||
`docs/traceability.md` mantiene una tabla sección → paquete → función → test. Se actualiza en el mismo PR que cualquier cambio de código normativo. Es el documento que se entrega al revisor externo junto con el spec, los fixtures y el corpus de mutaciones.
|
||||
|
||||
---
|
||||
|
||||
## 11. Decisiones a revisar
|
||||
|
||||
1. Decidida: ruta `g.activething.com/go/DateKeys`. Queda abierto si se pasa a una ruta en `datekeys.com` antes de publicar. Nota: el servidor ejecuta Gitea 1.18.3, sin Actions; los workflows de `.gitea/workflows` quedan listos para Gitea 1.21 o superior con un `act_runner`, y hasta entonces `scripts/check.sh` es el control obligatorio antes de cada push.
|
||||
2. Inglés para código, errores y commits.
|
||||
3. Licencia Apache-2.0 para el código, CC-BY-4.0 para el spec.
|
||||
4. Copiar el Bech32 interno de `age` frente a reimplementarlo.
|
||||
5. Depender del módulo `drand/v2` completo o extraer solo la verificación BLS a un paquete propio sobre `kyber-bls12381`. La primera opción es más simple; la segunda reduce el grafo y el ruido de `govulncheck`.
|
||||
6. Incluir en la librería un cliente de la Release API de DateKeys además del de relays drand, o dejarlo para el servidor.
|
||||
7. Validación CDDL en CI con toolchain externa, o solo como documento.
|
||||
8. Si `Open` exige `io.ReadSeeker` o acepta `io.Reader` y degrada el SHOULD.
|
||||
|
||||
---
|
||||
|
||||
## 12. Riesgos conocidos
|
||||
|
||||
- `kilic/bls12-381` archivado bajo `drand` y `tlock`. Mitigación: pinneado, vigilancia, plan de fork o de migración documentado en `SECURITY.md`.
|
||||
- `tlock` sin etiqueta desde agosto de 2024. Mitigación: pinneado por versión; solo se usa su núcleo exportado.
|
||||
- Fixtures sobre rondas pasadas dependen de que la firma embebida sea auténtica. Mitigación: se verifica BLS en cada ejecución contra la clave pinneada.
|
||||
- Desviación spec ↔ código. Mitigación: trazabilidad, política de cambios de la sección 76, y una segunda implementación en TypeScript como prueba de interoperabilidad.
|
||||
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "datekeys-app",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"description": "DateKeys in TypeScript: canonical CBOR codec, DKC1/DKK1 parsers, capsule inspector and, later, browser encryption and decryption.",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"testdata:sync": "node scripts/sync-testdata.mjs sync",
|
||||
"testdata:check": "node scripts/sync-testdata.mjs check --against ../datekeys-go"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "5.9.3",
|
||||
"vitest": "5.0.1"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env node
|
||||
// Vendors testdata/ from the Go reference implementation (g.activething.com/go/DateKeys)
|
||||
// at one pinned commit and verifies it. The Go fixtures and vectors are the source of
|
||||
// truth: this project never generates its own.
|
||||
//
|
||||
// node scripts/sync-testdata.mjs sync [--repo ../datekeys-go] [--commit HEAD]
|
||||
// node scripts/sync-testdata.mjs check [--against ../datekeys-go]
|
||||
//
|
||||
// sync copies every blob under testdata/ at the commit (read with git, so uncommitted
|
||||
// changes in the Go checkout are never picked up) and writes testdata/SOURCE.json with
|
||||
// the full commit id and the SHA-256 of each file. check verifies that the files on disk
|
||||
// match SOURCE.json exactly, with no missing or extra files; with --against it also
|
||||
// re-reads every blob from the Go repository at the recorded commit.
|
||||
// No dependencies: Node and git only.
|
||||
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, join, relative, resolve, sep } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const MODULE = 'g.activething.com/go/DateKeys';
|
||||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const TESTDATA = join(ROOT, 'testdata');
|
||||
const SOURCE = join(TESTDATA, 'SOURCE.json');
|
||||
const DEFAULT_REPO = resolve(ROOT, '..', 'datekeys-go');
|
||||
|
||||
function git(repo, args) {
|
||||
return execFileSync('git', ['-C', repo, ...args], { encoding: 'buffer', maxBuffer: 1 << 30 });
|
||||
}
|
||||
|
||||
function sha256(buf) {
|
||||
return createHash('sha256').update(buf).digest('hex');
|
||||
}
|
||||
|
||||
// Files under testdata/ at commit, as paths relative to testdata/ with forward slashes.
|
||||
function listBlobs(repo, commit) {
|
||||
const out = git(repo, ['ls-tree', '-r', '-z', '--full-tree', commit, '--', 'testdata']).toString('utf8');
|
||||
return out.split('\0').filter(Boolean).map(line => {
|
||||
const [meta, path] = line.split('\t');
|
||||
const [mode, type] = meta.split(' ');
|
||||
if (type !== 'blob' || mode === '120000') throw new Error(`unsupported entry ${path} (${mode} ${type})`);
|
||||
return path.slice('testdata/'.length);
|
||||
}).sort();
|
||||
}
|
||||
|
||||
function readBlob(repo, commit, path) {
|
||||
return git(repo, ['cat-file', 'blob', `${commit}:testdata/${path}`]);
|
||||
}
|
||||
|
||||
// Files on disk under testdata/, excluding SOURCE.json.
|
||||
function listLocal(dir = TESTDATA) {
|
||||
let files = [];
|
||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||
const full = join(dir, entry.name);
|
||||
if (entry.isDirectory()) files = files.concat(listLocal(full));
|
||||
else if (full !== SOURCE) files.push(relative(TESTDATA, full).split(sep).join('/'));
|
||||
}
|
||||
return files.sort();
|
||||
}
|
||||
|
||||
function sync(repo, rev) {
|
||||
const commit = git(repo, ['rev-parse', '--verify', `${rev}^{commit}`]).toString('utf8').trim();
|
||||
const paths = listBlobs(repo, commit);
|
||||
if (paths.length === 0) throw new Error(`no testdata/ at ${commit}`);
|
||||
// Read everything before touching the local copy, so a failed read leaves it intact.
|
||||
const blobs = paths.map(path => [path, readBlob(repo, commit, path)]);
|
||||
rmSync(TESTDATA, { recursive: true, force: true });
|
||||
const files = {};
|
||||
for (const [path, data] of blobs) {
|
||||
const target = join(TESTDATA, ...path.split('/'));
|
||||
mkdirSync(dirname(target), { recursive: true });
|
||||
writeFileSync(target, data);
|
||||
files[path] = sha256(data);
|
||||
}
|
||||
writeFileSync(SOURCE, JSON.stringify({ module: MODULE, commit, files }, null, 2) + '\n');
|
||||
console.log(`testdata: ${paths.length} files from ${MODULE} at ${commit}`);
|
||||
}
|
||||
|
||||
function check(against) {
|
||||
const source = JSON.parse(readFileSync(SOURCE, 'utf8'));
|
||||
const expected = Object.keys(source.files).sort();
|
||||
const actual = listLocal();
|
||||
const errors = [];
|
||||
for (const path of expected) {
|
||||
if (!actual.includes(path)) { errors.push(`missing ${path}`); continue; }
|
||||
if (sha256(readFileSync(join(TESTDATA, ...path.split('/')))) !== source.files[path]) errors.push(`modified ${path}`);
|
||||
}
|
||||
for (const path of actual) if (!(path in source.files)) errors.push(`extra ${path}`);
|
||||
if (against) {
|
||||
const upstream = listBlobs(against, source.commit);
|
||||
if (upstream.join('\n') !== expected.join('\n')) errors.push(`file list differs from ${source.commit}`);
|
||||
for (const path of upstream) {
|
||||
if (sha256(readBlob(against, source.commit, path)) !== source.files[path]) errors.push(`differs from upstream ${path}`);
|
||||
}
|
||||
}
|
||||
if (errors.length) {
|
||||
for (const e of errors) console.error(`testdata: ${e}`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`testdata: ${expected.length} files match ${source.module} at ${source.commit}${against ? ' (verified against the repository)' : ''}`);
|
||||
}
|
||||
|
||||
function option(args, name, fallback) {
|
||||
const i = args.indexOf(name);
|
||||
if (i === -1) return fallback;
|
||||
if (i + 1 >= args.length) throw new Error(`${name} needs a value`);
|
||||
return args[i + 1];
|
||||
}
|
||||
|
||||
const [command, ...args] = process.argv.slice(2);
|
||||
try {
|
||||
if (command === 'sync') sync(resolve(option(args, '--repo', DEFAULT_REPO)), option(args, '--commit', 'HEAD'));
|
||||
else if (command === 'check') {
|
||||
const against = option(args, '--against', null);
|
||||
check(against && resolve(against));
|
||||
} else {
|
||||
console.error('usage: sync-testdata.mjs sync [--repo PATH] [--commit REV] | check [--against PATH]');
|
||||
process.exit(2);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`testdata: ${err.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
{
|
||||
"module": "g.activething.com/go/DateKeys",
|
||||
"commit": "5719f6a4b3654f608091e32290d9fcfbc7e38062",
|
||||
"files": {
|
||||
"fixtures/empty_payload.dkc": "871e9bf05b52bbae17f3adfbbf97b46e7f0e53aa8f57bcaa506e43f36f53a9d4",
|
||||
"fixtures/empty_payload.json": "584f7a5a8982d5a9184ee6a4699fa39ad82c92a3d01d573c3e94e603db9b0956",
|
||||
"fixtures/empty_payload.plaintext": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"fixtures/time_and_key_portable.dkc": "2e97878078bae6358037a9c264f379a3cbe839f767d69836b0343f35657b2972",
|
||||
"fixtures/time_and_key_portable.dkk": "e528fa2c832c91119f0684bb9d6fb3c4c2d0d55183482890e7c4fe92f668426a",
|
||||
"fixtures/time_and_key_portable.dkk.json": "6f940bc94f4eb13e41ccbbb76c69098ec9c5bc32cb86963e57b839c56ebf9534",
|
||||
"fixtures/time_and_key_portable.json": "74936213a5d481dc216ad36115523bebc6973823cf89782b50a96eb4649ffcbe",
|
||||
"fixtures/time_and_key_portable.plaintext": "937492203d207d6fe36161b8696bf1f05b8b4cc56d855c44853f4b76aad3a05b",
|
||||
"fixtures/time_and_key_recipients.dkc": "69ac110380f5d768b5b6afaa157a50ed17d8ceccfbd4604ffa5b6da38539b635",
|
||||
"fixtures/time_and_key_recipients.dkk": "19f6c47150c3194712d454f43c7392b7344e6b4e7b074d83e9ca5f563a8e072f",
|
||||
"fixtures/time_and_key_recipients.dkk.json": "e2f8adde5da559be559647951d95b949c7eec6bffb1fea8e477a5b5521971f3a",
|
||||
"fixtures/time_and_key_recipients.json": "446815de9bff62e6dcc00a106ba7ab05a3601c748b6de8c53a0c3ef5ce7ce843",
|
||||
"fixtures/time_and_key_recipients.plaintext": "0e9fd50e98a85953aa9cf07a11ee3c62bb3d7622f344f1c6ce744d1ed111659f",
|
||||
"fixtures/time_only.dkc": "99e915810d595f1092700b728f5e5081d78efe83f5343e76325b1bcc2c33ccf2",
|
||||
"fixtures/time_only.json": "a38a6287b10ad944669f07b3327cd7405e9f76778240ec935a408580261d0e84",
|
||||
"fixtures/time_only.plaintext": "53b8ee821fb7b678e89d4f93da1812339f6cc1ab83aac6ed1432db99df784be5",
|
||||
"fixtures/time_only_extensions.dkc": "1e7effd58016d9447f9a2e7c9645c658604979c4e35af675d1dc6c4ae12ac444",
|
||||
"fixtures/time_only_extensions.json": "c70ead5b8e196b942f89dc6c105e651d2ed2acc4289d1fd2dca98bd9eeb83293",
|
||||
"fixtures/time_only_extensions.plaintext": "1129768e195e2f1e50b7a6f926b6eebef120212c29b5642c8a662c503b2a9131",
|
||||
"vectors/dk1.json": "5127b8015b93c8f2b7cc92b75cbbad0a22073b39381901851a79656383d83426",
|
||||
"vectors/profile_quicknet.json": "caa840b2ae49b2c220fb31c0a71ade98d170c1d5ab2e2210e980b98c88d58bec",
|
||||
"vectors/quicknet_rounds.json": "fb9fb27671e5c1db2a80a2492e82fe3a702a5ddbf7afffe95c64f842d07bc54f"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@ -0,0 +1,121 @@
|
||||
{
|
||||
"description": "time_only capsule with an empty payload",
|
||||
"spec": "0.8.1",
|
||||
"file": "empty_payload.dkc",
|
||||
"sha256": "871e9bf05b52bbae17f3adfbbf97b46e7f0e53aa8f57bcaa506e43f36f53a9d4",
|
||||
"release": {
|
||||
"round": 1001,
|
||||
"signature": "b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b25883abf2853d10337fb8fa41"
|
||||
},
|
||||
"prelude": "444b43310100000000000079000001be",
|
||||
"public_header": "a5006a646174656b657963617001010250ab10174561a9a19a6d9dc9ab1ef59c66037853646b315f65794a325a584a7a61573975496a6f784c434a755a58523362334a72496a6f695a4746305a57746c65584d3663585670593274755a585136646a45694c434a79623356755a4349364d5441774d58300400",
|
||||
"datekey": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6MTAwMX0",
|
||||
"capsule_id": "ab10174561a9a19a6d9dc9ab1ef59c66",
|
||||
"access_policy": "time_only",
|
||||
"structure": "time_only",
|
||||
"unlock_at": "2023-08-23T15:59:27Z",
|
||||
"header_binding": "33186a4f03d79eb8e28dbb82d2538ada45b68cd4f2ed6d17fa87b211e54f4d58",
|
||||
"outer_stanzas": [
|
||||
{
|
||||
"type": "tlock",
|
||||
"args": [
|
||||
"1001",
|
||||
"52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971"
|
||||
]
|
||||
}
|
||||
],
|
||||
"payload_stanzas": [
|
||||
{
|
||||
"type": "X25519",
|
||||
"args": [
|
||||
"kT/xASH7NSOUvlk9XfIAh37JzSq6kWxPKVkSzflygjY"
|
||||
]
|
||||
}
|
||||
],
|
||||
"control_cbor": "a40070646174656b6579732d636f6e74726f6c010102582033186a4f03d79eb8e28dbb82d2538ada45b68cd4f2ed6d17fa87b211e54f4d58035820cd5ca142d51386860bf4ae4ae16740d498ef70ff534084acf5f4005b74f278c3",
|
||||
"payload_identity": "cd5ca142d51386860bf4ae4ae16740d498ef70ff534084acf5f4005b74f278c3",
|
||||
"plaintext_file": "empty_payload.plaintext",
|
||||
"plaintext_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"stages": [
|
||||
{
|
||||
"step": 1,
|
||||
"name": "parse DKC1",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"name": "prelude",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"name": "public header",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 4,
|
||||
"name": "header validation",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 5,
|
||||
"name": "sealed control structure",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 6,
|
||||
"name": "payload structure",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 7,
|
||||
"name": "condition",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 8,
|
||||
"name": "tlock stanza",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 9,
|
||||
"name": "release",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 10,
|
||||
"name": "release verification",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 11,
|
||||
"name": "open sealed control",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 12,
|
||||
"name": "policy structure",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 14,
|
||||
"name": "control",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 15,
|
||||
"name": "header binding",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 16,
|
||||
"name": "payload identity",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 18,
|
||||
"name": "commit",
|
||||
"ok": true
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@ -0,0 +1,13 @@
|
||||
{
|
||||
"description": "portable X25519 .dkk of time_and_key_portable.dkc",
|
||||
"spec": "0.8.1",
|
||||
"file": "time_and_key_portable.dkk",
|
||||
"sha256": "e528fa2c832c91119f0684bb9d6fb3c4c2d0d55183482890e7c4fe92f668426a",
|
||||
"credential_id": "3955e944a3c60cfa1fd6485e9693c77d",
|
||||
"capsule_id": "448e134a13457c319cab7fceaf7ffa1f",
|
||||
"access_type": "x25519",
|
||||
"access_material": "3d5e4d51f21236ecff40d89c0adcf9aa325a24b91b46fd9d1afa482f54b2d2c6",
|
||||
"capsule_digest": "2e97878078bae6358037a9c264f379a3cbe839f767d69836b0343f35657b2972",
|
||||
"capsule": "time_and_key_portable.dkc",
|
||||
"expected_result": "opens INNER_ACCESS_AGE of time_and_key_portable.dkc and yields its CONTROL_CBOR"
|
||||
}
|
||||
@ -0,0 +1,140 @@
|
||||
{
|
||||
"description": "time_and_key capsule whose only recipient is a portable .dkk",
|
||||
"spec": "0.8.1",
|
||||
"file": "time_and_key_portable.dkc",
|
||||
"sha256": "2e97878078bae6358037a9c264f379a3cbe839f767d69836b0343f35657b2972",
|
||||
"release": {
|
||||
"round": 1000,
|
||||
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"
|
||||
},
|
||||
"prelude": "444b4331010000000000007900000286",
|
||||
"public_header": "a5006a646174656b657963617001010250448e134a13457c319cab7fceaf7ffa1f037853646b315f65794a325a584a7a61573975496a6f784c434a755a58523362334a72496a6f695a4746305a57746c65584d3663585670593274755a585136646a45694c434a79623356755a4349364d5441774d48300401",
|
||||
"datekey": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6MTAwMH0",
|
||||
"capsule_id": "448e134a13457c319cab7fceaf7ffa1f",
|
||||
"access_policy": "time_and_key",
|
||||
"structure": "time_and_key",
|
||||
"unlock_at": "2023-08-23T15:59:24Z",
|
||||
"header_binding": "841fe789895abdd0ffecb0eb7562f4c4b4dfd0d1f8ec6fd251adbe1a89d5ab5f",
|
||||
"outer_stanzas": [
|
||||
{
|
||||
"type": "tlock",
|
||||
"args": [
|
||||
"1000",
|
||||
"52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971"
|
||||
]
|
||||
}
|
||||
],
|
||||
"payload_stanzas": [
|
||||
{
|
||||
"type": "X25519",
|
||||
"args": [
|
||||
"/g3xW+kK9u65qfWLzS5huoFB/JIc9EqG3QBOfuO9PVQ"
|
||||
]
|
||||
}
|
||||
],
|
||||
"inner_stanzas": [
|
||||
{
|
||||
"type": "X25519",
|
||||
"args": [
|
||||
"FyePxd/IAO/2FpE3kQgiidbDsxRVuNM/eEI3SDbbamc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"access_key_file": "time_and_key_portable.dkk",
|
||||
"control_cbor": "a40070646174656b6579732d636f6e74726f6c0101025820841fe789895abdd0ffecb0eb7562f4c4b4dfd0d1f8ec6fd251adbe1a89d5ab5f0358202536e99b16373ca8d118695c600fd652541367b0198dbfaba9362a98f9fa70cb",
|
||||
"payload_identity": "2536e99b16373ca8d118695c600fd652541367b0198dbfaba9362a98f9fa70cb",
|
||||
"plaintext_file": "time_and_key_portable.plaintext",
|
||||
"plaintext_sha256": "937492203d207d6fe36161b8696bf1f05b8b4cc56d855c44853f4b76aad3a05b",
|
||||
"stages": [
|
||||
{
|
||||
"step": 1,
|
||||
"name": "parse DKC1",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"name": "prelude",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"name": "public header",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 4,
|
||||
"name": "header validation",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 5,
|
||||
"name": "sealed control structure",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 6,
|
||||
"name": "payload structure",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 7,
|
||||
"name": "condition",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 8,
|
||||
"name": "tlock stanza",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 9,
|
||||
"name": "access credential",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 9,
|
||||
"name": "release",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 10,
|
||||
"name": "release verification",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 11,
|
||||
"name": "open sealed control",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 12,
|
||||
"name": "policy structure",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 13,
|
||||
"name": "open access layer",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 14,
|
||||
"name": "control",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 15,
|
||||
"name": "header binding",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 16,
|
||||
"name": "payload identity",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 18,
|
||||
"name": "commit",
|
||||
"ok": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
DateKeys fixture opened with a portable .dkk.
|
||||
Binary file not shown.
Binary file not shown.
@ -0,0 +1,13 @@
|
||||
{
|
||||
"description": "portable X25519 .dkk of time_and_key_recipients.dkc",
|
||||
"spec": "0.8.1",
|
||||
"file": "time_and_key_recipients.dkk",
|
||||
"sha256": "19f6c47150c3194712d454f43c7392b7344e6b4e7b074d83e9ca5f563a8e072f",
|
||||
"credential_id": "b89292aedf6d05d584cec9a871ce8735",
|
||||
"capsule_id": "c75dfc8e9c576d1369910664df93693a",
|
||||
"access_type": "x25519",
|
||||
"access_material": "42d6d897097fd5af2772e058db17920afe1390e2856139bc2f800294564dd7ac",
|
||||
"capsule_digest": "69ac110380f5d768b5b6afaa157a50ed17d8ceccfbd4604ffa5b6da38539b635",
|
||||
"capsule": "time_and_key_recipients.dkc",
|
||||
"expected_result": "opens INNER_ACCESS_AGE of time_and_key_recipients.dkc and yields its CONTROL_CBOR"
|
||||
}
|
||||
@ -0,0 +1,156 @@
|
||||
{
|
||||
"description": "time_and_key capsule for two known X25519 recipients and a portable .dkk",
|
||||
"spec": "0.8.1",
|
||||
"file": "time_and_key_recipients.dkc",
|
||||
"sha256": "69ac110380f5d768b5b6afaa157a50ed17d8ceccfbd4604ffa5b6da38539b635",
|
||||
"release": {
|
||||
"round": 1001,
|
||||
"signature": "b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b25883abf2853d10337fb8fa41"
|
||||
},
|
||||
"prelude": "444b433101000000000000790000034a",
|
||||
"public_header": "a5006a646174656b657963617001010250c75dfc8e9c576d1369910664df93693a037853646b315f65794a325a584a7a61573975496a6f784c434a755a58523362334a72496a6f695a4746305a57746c65584d3663585670593274755a585136646a45694c434a79623356755a4349364d5441774d58300401",
|
||||
"datekey": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6MTAwMX0",
|
||||
"capsule_id": "c75dfc8e9c576d1369910664df93693a",
|
||||
"access_policy": "time_and_key",
|
||||
"structure": "time_and_key",
|
||||
"unlock_at": "2023-08-23T15:59:27Z",
|
||||
"header_binding": "5d789b4bde52beecbad4b80e9b0a8fec8b59b6a84263af6d022cd1eccfded0e2",
|
||||
"outer_stanzas": [
|
||||
{
|
||||
"type": "tlock",
|
||||
"args": [
|
||||
"1001",
|
||||
"52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971"
|
||||
]
|
||||
}
|
||||
],
|
||||
"payload_stanzas": [
|
||||
{
|
||||
"type": "X25519",
|
||||
"args": [
|
||||
"ew9JpO0AGTGmWXpZCEHsSrN0Ey2kSjzphbokfjCMoF4"
|
||||
]
|
||||
}
|
||||
],
|
||||
"inner_stanzas": [
|
||||
{
|
||||
"type": "X25519",
|
||||
"args": [
|
||||
"sOnq+vJxTJVHlPwhfXd2LovGNqRMuyS3e7qJQm8at0E"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "X25519",
|
||||
"args": [
|
||||
"T8NHQ9xo+IaBvEoqirkEgwdbFzOUt19moVhfvkebdUY"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "X25519",
|
||||
"args": [
|
||||
"tIu+bu/q+z3ACTYdwpolbaSwA7PXv4QVjXbcrgOXAAM"
|
||||
]
|
||||
}
|
||||
],
|
||||
"access_key_file": "time_and_key_recipients.dkk",
|
||||
"identities": [
|
||||
"AGE-SECRET-KEY-1DPM6CQMQV3665FK762HTVC37XAY6X99MM4YLJ9J0J2DQD7K63GCSG5TMCK",
|
||||
"AGE-SECRET-KEY-15MEM79HAM2XQ79HN5QVCLECUDM4JQQKWEE9JWR3VV2FWK033AXPQQ2422Y"
|
||||
],
|
||||
"control_cbor": "a40070646174656b6579732d636f6e74726f6c01010258205d789b4bde52beecbad4b80e9b0a8fec8b59b6a84263af6d022cd1eccfded0e20358205d3f4172eca48e5d5b2570208cfe3298c4735f1d77ceaed958bcccb8eec18a12",
|
||||
"payload_identity": "5d3f4172eca48e5d5b2570208cfe3298c4735f1d77ceaed958bcccb8eec18a12",
|
||||
"plaintext_file": "time_and_key_recipients.plaintext",
|
||||
"plaintext_sha256": "0e9fd50e98a85953aa9cf07a11ee3c62bb3d7622f344f1c6ce744d1ed111659f",
|
||||
"stages": [
|
||||
{
|
||||
"step": 1,
|
||||
"name": "parse DKC1",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"name": "prelude",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"name": "public header",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 4,
|
||||
"name": "header validation",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 5,
|
||||
"name": "sealed control structure",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 6,
|
||||
"name": "payload structure",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 7,
|
||||
"name": "condition",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 8,
|
||||
"name": "tlock stanza",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 9,
|
||||
"name": "access credential",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 9,
|
||||
"name": "release",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 10,
|
||||
"name": "release verification",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 11,
|
||||
"name": "open sealed control",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 12,
|
||||
"name": "policy structure",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 13,
|
||||
"name": "open access layer",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 14,
|
||||
"name": "control",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 15,
|
||||
"name": "header binding",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 16,
|
||||
"name": "payload identity",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 18,
|
||||
"name": "commit",
|
||||
"ok": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
DateKeys fixture for several recipients.
|
||||
Binary file not shown.
@ -0,0 +1,121 @@
|
||||
{
|
||||
"description": "time_only capsule, two STREAM chunks, no extensions",
|
||||
"spec": "0.8.1",
|
||||
"file": "time_only.dkc",
|
||||
"sha256": "99e915810d595f1092700b728f5e5081d78efe83f5343e76325b1bcc2c33ccf2",
|
||||
"release": {
|
||||
"round": 1000,
|
||||
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"
|
||||
},
|
||||
"prelude": "444b43310100000000000079000001be",
|
||||
"public_header": "a5006a646174656b657963617001010250ad4d676812b134ff8a3de263f77018b4037853646b315f65794a325a584a7a61573975496a6f784c434a755a58523362334a72496a6f695a4746305a57746c65584d3663585670593274755a585136646a45694c434a79623356755a4349364d5441774d48300400",
|
||||
"datekey": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6MTAwMH0",
|
||||
"capsule_id": "ad4d676812b134ff8a3de263f77018b4",
|
||||
"access_policy": "time_only",
|
||||
"structure": "time_only",
|
||||
"unlock_at": "2023-08-23T15:59:24Z",
|
||||
"header_binding": "8e1d05df55bc626a579759d54d436b512b7bfc71039d3c12c1875a5b475f2417",
|
||||
"outer_stanzas": [
|
||||
{
|
||||
"type": "tlock",
|
||||
"args": [
|
||||
"1000",
|
||||
"52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971"
|
||||
]
|
||||
}
|
||||
],
|
||||
"payload_stanzas": [
|
||||
{
|
||||
"type": "X25519",
|
||||
"args": [
|
||||
"LvR2+5baviJPeIiyw96VfTW1GnzTw3DbWIPGuq9amEw"
|
||||
]
|
||||
}
|
||||
],
|
||||
"control_cbor": "a40070646174656b6579732d636f6e74726f6c01010258208e1d05df55bc626a579759d54d436b512b7bfc71039d3c12c1875a5b475f2417035820e63d28ff1a6d1975263db49ff80c3bf949737d20aeedcc9539e648ffd330082b",
|
||||
"payload_identity": "e63d28ff1a6d1975263db49ff80c3bf949737d20aeedcc9539e648ffd330082b",
|
||||
"plaintext_file": "time_only.plaintext",
|
||||
"plaintext_sha256": "53b8ee821fb7b678e89d4f93da1812339f6cc1ab83aac6ed1432db99df784be5",
|
||||
"stages": [
|
||||
{
|
||||
"step": 1,
|
||||
"name": "parse DKC1",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"name": "prelude",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"name": "public header",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 4,
|
||||
"name": "header validation",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 5,
|
||||
"name": "sealed control structure",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 6,
|
||||
"name": "payload structure",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 7,
|
||||
"name": "condition",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 8,
|
||||
"name": "tlock stanza",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 9,
|
||||
"name": "release",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 10,
|
||||
"name": "release verification",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 11,
|
||||
"name": "open sealed control",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 12,
|
||||
"name": "policy structure",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 14,
|
||||
"name": "control",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 15,
|
||||
"name": "header binding",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 16,
|
||||
"name": "payload identity",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 18,
|
||||
"name": "commit",
|
||||
"ok": true
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -0,0 +1,137 @@
|
||||
{
|
||||
"description": "time_only capsule with a noncritical PUBLIC_HEADER extension and a noncritical CONTROL_CBOR extension",
|
||||
"spec": "0.8.1",
|
||||
"file": "time_only_extensions.dkc",
|
||||
"sha256": "1e7effd58016d9447f9a2e7c9645c658604979c4e35af675d1dc6c4ae12ac444",
|
||||
"release": {
|
||||
"round": 2000,
|
||||
"signature": "b6cb8f482a0b15d45936a4c4ea08e98a087e71787caee3f4d07a8a9843b1bc5423c6b3c22f446488b3137eaca799c77e"
|
||||
},
|
||||
"prelude": "444b4331010000000000009f000001e2",
|
||||
"public_header": "a6006a646174656b657963617001010250d2fd9af55dc0253132fb36b8dc0d016b037853646b315f65794a325a584a7a61573975496a6f784c434a755a58523362334a72496a6f695a4746305a57746c65584d3663585670593274755a585136646a45694c434a79623356755a4349364d6a41774d483004000681a300716f72672e6578616d706c652e6c6162656c0101026c7075626c6963206c6162656c",
|
||||
"datekey": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6MjAwMH0",
|
||||
"capsule_id": "d2fd9af55dc0253132fb36b8dc0d016b",
|
||||
"access_policy": "time_only",
|
||||
"structure": "time_only",
|
||||
"unlock_at": "2023-08-23T16:49:24Z",
|
||||
"header_binding": "a4fa1e3c5a47a01313173cdf23df157ff8c949011df95dde8f092ff6749dc154",
|
||||
"outer_stanzas": [
|
||||
{
|
||||
"type": "tlock",
|
||||
"args": [
|
||||
"2000",
|
||||
"52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971"
|
||||
]
|
||||
}
|
||||
],
|
||||
"payload_stanzas": [
|
||||
{
|
||||
"type": "X25519",
|
||||
"args": [
|
||||
"pUUIuCTHAZ3+kpMwJQQ9dc3EJO1zPagucu+VdFovSy0"
|
||||
]
|
||||
}
|
||||
],
|
||||
"control_cbor": "a50070646174656b6579732d636f6e74726f6c0101025820a4fa1e3c5a47a01313173cdf23df157ff8c949011df95dde8f092ff6749dc1540358201b27d7029c50dc20ef1f9269a1a944a1384f526f9ea76cdc9c76a6456bb1fbb80581a300706f72672e6578616d706c652e6e6f7465010202a2616e07667365616c6564f5",
|
||||
"payload_identity": "1b27d7029c50dc20ef1f9269a1a944a1384f526f9ea76cdc9c76a6456bb1fbb8",
|
||||
"plaintext_file": "time_only_extensions.plaintext",
|
||||
"plaintext_sha256": "1129768e195e2f1e50b7a6f926b6eebef120212c29b5642c8a662c503b2a9131",
|
||||
"header_extensions": [
|
||||
{
|
||||
"critical": false,
|
||||
"id": "org.example.label",
|
||||
"version": 1,
|
||||
"data": "6c7075626c6963206c6162656c"
|
||||
}
|
||||
],
|
||||
"control_extensions": [
|
||||
{
|
||||
"critical": false,
|
||||
"id": "org.example.note",
|
||||
"version": 2,
|
||||
"data": "a2616e07667365616c6564f5"
|
||||
}
|
||||
],
|
||||
"stages": [
|
||||
{
|
||||
"step": 1,
|
||||
"name": "parse DKC1",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"name": "prelude",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"name": "public header",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 4,
|
||||
"name": "header validation",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 5,
|
||||
"name": "sealed control structure",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 6,
|
||||
"name": "payload structure",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 7,
|
||||
"name": "condition",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 8,
|
||||
"name": "tlock stanza",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 9,
|
||||
"name": "release",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 10,
|
||||
"name": "release verification",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 11,
|
||||
"name": "open sealed control",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 12,
|
||||
"name": "policy structure",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 14,
|
||||
"name": "control",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 15,
|
||||
"name": "header binding",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 16,
|
||||
"name": "payload identity",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": 18,
|
||||
"name": "commit",
|
||||
"ok": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
DateKeys fixture with extensions.
|
||||
@ -0,0 +1,153 @@
|
||||
{
|
||||
"spec": "0.8.1",
|
||||
"description": "Canonical dk1_ strings and rejected encodings (spec §18, §19, §66), generated by the reference implementation.",
|
||||
"vectors": [
|
||||
{
|
||||
"name": "round 1",
|
||||
"network": "datekeys:quicknet:v1",
|
||||
"round": 1,
|
||||
"canonical_json": "{\"version\":1,\"network\":\"datekeys:quicknet:v1\",\"round\":1}",
|
||||
"base64url": "eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6MX0",
|
||||
"dk1": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6MX0"
|
||||
},
|
||||
{
|
||||
"name": "round 1000",
|
||||
"network": "datekeys:quicknet:v1",
|
||||
"round": 1000,
|
||||
"canonical_json": "{\"version\":1,\"network\":\"datekeys:quicknet:v1\",\"round\":1000}",
|
||||
"base64url": "eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6MTAwMH0",
|
||||
"dk1": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6MTAwMH0"
|
||||
},
|
||||
{
|
||||
"name": "normative 2030-01-01 round",
|
||||
"network": "datekeys:quicknet:v1",
|
||||
"round": 66884212,
|
||||
"canonical_json": "{\"version\":1,\"network\":\"datekeys:quicknet:v1\",\"round\":66884212}",
|
||||
"base64url": "eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6NjY4ODQyMTJ9",
|
||||
"dk1": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6NjY4ODQyMTJ9"
|
||||
},
|
||||
{
|
||||
"name": "last Quicknet round",
|
||||
"network": "datekeys:quicknet:v1",
|
||||
"round": 83903165811,
|
||||
"canonical_json": "{\"version\":1,\"network\":\"datekeys:quicknet:v1\",\"round\":83903165811}",
|
||||
"base64url": "eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6ODM5MDMxNjU4MTF9",
|
||||
"dk1": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6ODM5MDMxNjU4MTF9"
|
||||
},
|
||||
{
|
||||
"name": "whitespace in JSON",
|
||||
"input": "dk1_eyJ2ZXJzaW9uIjogMSwgIm5ldHdvcmsiOiAiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCAicm91bmQiOiA2Njg4NDIxMn0",
|
||||
"error": "ERR_DATEKEY_NON_CANONICAL"
|
||||
},
|
||||
{
|
||||
"name": "keys reordered",
|
||||
"input": "dk1_eyJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJ2ZXJzaW9uIjoxLCJyb3VuZCI6NjY4ODQyMTJ9",
|
||||
"error": "ERR_DATEKEY_NON_CANONICAL"
|
||||
},
|
||||
{
|
||||
"name": "trailing whitespace",
|
||||
"input": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6NjY4ODQyMTJ9Cg",
|
||||
"error": "ERR_DATEKEY_NON_CANONICAL"
|
||||
},
|
||||
{
|
||||
"name": "exponent notation",
|
||||
"input": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6Ni42ODg0MjEyZTd9",
|
||||
"error": "ERR_DATEKEY_NON_CANONICAL"
|
||||
},
|
||||
{
|
||||
"name": "fraction notation",
|
||||
"input": "dk1_eyJ2ZXJzaW9uIjoxLjAsIm5ldHdvcmsiOiJkYXRla2V5czpxdWlja25ldDp2MSIsInJvdW5kIjo2Njg4NDIxMn0",
|
||||
"error": "ERR_DATEKEY_NON_CANONICAL"
|
||||
},
|
||||
{
|
||||
"name": "escaped character",
|
||||
"input": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXNcdTAwM2FxdWlja25ldDp2MSIsInJvdW5kIjo2Njg4NDIxMn0",
|
||||
"error": "ERR_DATEKEY_NON_CANONICAL"
|
||||
},
|
||||
{
|
||||
"name": "duplicate key",
|
||||
"input": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6MSwicm91bmQiOjY2ODg0MjEyfQ",
|
||||
"error": "ERR_DATEKEY_NON_CANONICAL"
|
||||
},
|
||||
{
|
||||
"name": "padded Base64URL",
|
||||
"input": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6MTAwMH0=",
|
||||
"error": "ERR_DATEKEY_NON_CANONICAL"
|
||||
},
|
||||
{
|
||||
"name": "non-zero trailing bits",
|
||||
"input": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6MTAwMH1",
|
||||
"error": "ERR_DATEKEY_NON_CANONICAL"
|
||||
},
|
||||
{
|
||||
"name": "extra field",
|
||||
"input": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6NjY4ODQyMTIsInB1YmxpY19rZXkiOiIwMCJ9",
|
||||
"error": "ERR_DATEKEY_INVALID"
|
||||
},
|
||||
{
|
||||
"name": "missing field",
|
||||
"input": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEifQ",
|
||||
"error": "ERR_DATEKEY_INVALID"
|
||||
},
|
||||
{
|
||||
"name": "version 2",
|
||||
"input": "dk1_eyJ2ZXJzaW9uIjoyLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6NjY4ODQyMTJ9",
|
||||
"error": "ERR_DATEKEY_INVALID"
|
||||
},
|
||||
{
|
||||
"name": "round 0",
|
||||
"input": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6MH0",
|
||||
"error": "ERR_DATEKEY_INVALID"
|
||||
},
|
||||
{
|
||||
"name": "negative round",
|
||||
"input": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6LTF9",
|
||||
"error": "ERR_DATEKEY_INVALID"
|
||||
},
|
||||
{
|
||||
"name": "fractional round",
|
||||
"input": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6MS41fQ",
|
||||
"error": "ERR_DATEKEY_INVALID"
|
||||
},
|
||||
{
|
||||
"name": "round above 2^53-1",
|
||||
"input": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6OTAwNzE5OTI1NDc0MDk5Mn0",
|
||||
"error": "ERR_DATEKEY_INVALID"
|
||||
},
|
||||
{
|
||||
"name": "round as string",
|
||||
"input": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6IjY2ODg0MjEyIn0",
|
||||
"error": "ERR_DATEKEY_INVALID"
|
||||
},
|
||||
{
|
||||
"name": "uppercase network",
|
||||
"input": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiREFURUtFWVM6UVVJQ0tORVQ6VjEiLCJyb3VuZCI6NjY4ODQyMTJ9",
|
||||
"error": "ERR_DATEKEY_INVALID"
|
||||
},
|
||||
{
|
||||
"name": "trailing data",
|
||||
"input": "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6NjY4ODQyMTJ9eA",
|
||||
"error": "ERR_DATEKEY_INVALID"
|
||||
},
|
||||
{
|
||||
"name": "byte order mark",
|
||||
"input": "dk1_77u_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6NjY4ODQyMTJ9",
|
||||
"error": "ERR_DATEKEY_INVALID"
|
||||
},
|
||||
{
|
||||
"name": "not Base64",
|
||||
"input": "dk1_!!!",
|
||||
"error": "ERR_DATEKEY_INVALID"
|
||||
},
|
||||
{
|
||||
"name": "missing prefix",
|
||||
"input": "eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6NjY4ODQyMTJ9",
|
||||
"error": "ERR_DATEKEY_INVALID"
|
||||
},
|
||||
{
|
||||
"name": "uppercase prefix",
|
||||
"input": "DK1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6NjY4ODQyMTJ9",
|
||||
"error": "ERR_DATEKEY_INVALID"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
{
|
||||
"spec": "0.8.1",
|
||||
"description": "Quicknet Provider Profile V1: exact Deterministic CBOR and profile_hash (spec §11, §12, §75 item 2), generated by the reference implementation.",
|
||||
"profile_id": "datekeys:quicknet:v1",
|
||||
"provider": "drand",
|
||||
"network": "quicknet",
|
||||
"chain_hash": "52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971",
|
||||
"public_key": "83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a",
|
||||
"period_seconds": 3,
|
||||
"genesis_time": 1692803367,
|
||||
"genesis_seed": "f477d5c89f21a17c863a7f937c6a6d15859414d2be09cd448d4279af331c5d3e",
|
||||
"scheme": "bls-unchained-g1-rfc9380",
|
||||
"canonical_cbor": "ab007819646174656b6579732d70726f76696465722d70726f66696c6501010274646174656b6579733a717569636b6e65743a763103656472616e640468717569636b6e657405582052db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e97106586083cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a0703081a64e62127097818626c732d756e636861696e65642d67312d726663393338300a5820f477d5c89f21a17c863a7f937c6a6d15859414d2be09cd448d4279af331c5d3e",
|
||||
"profile_hash": "4147645109798ecbc9f630c2f709835bb5846fe7911a6bd5c5755e25ade2ada4"
|
||||
}
|
||||
@ -0,0 +1,107 @@
|
||||
{
|
||||
"spec": "0.8.1",
|
||||
"profile": "datekeys:quicknet:v1",
|
||||
"description": "Quicknet date to round resolution (spec §15, §16, §65), generated by the reference implementation.",
|
||||
"vectors": [
|
||||
{
|
||||
"name": "genesis exactly: round 1",
|
||||
"requested": "2023-08-23T15:09:27Z",
|
||||
"round": 1,
|
||||
"effective": "2023-08-23T15:09:27Z"
|
||||
},
|
||||
{
|
||||
"name": "genesis + 1ns: next round",
|
||||
"requested": "2023-08-23T15:09:27.000000001Z",
|
||||
"round": 2,
|
||||
"effective": "2023-08-23T15:09:30Z"
|
||||
},
|
||||
{
|
||||
"name": "genesis + 1s",
|
||||
"requested": "2023-08-23T15:09:28Z",
|
||||
"round": 2,
|
||||
"effective": "2023-08-23T15:09:30Z"
|
||||
},
|
||||
{
|
||||
"name": "genesis + one period: round 2",
|
||||
"requested": "2023-08-23T15:09:30Z",
|
||||
"round": 2,
|
||||
"effective": "2023-08-23T15:09:30Z"
|
||||
},
|
||||
{
|
||||
"name": "genesis + one period + 1ns: round 3",
|
||||
"requested": "2023-08-23T15:09:30.000000001Z",
|
||||
"round": 3,
|
||||
"effective": "2023-08-23T15:09:33Z"
|
||||
},
|
||||
{
|
||||
"name": "genesis - 1s: before the profile",
|
||||
"requested": "2023-08-23T15:09:26Z",
|
||||
"error": "ERR_DATEKEY_INVALID"
|
||||
},
|
||||
{
|
||||
"name": "round 1000 boundary exactly",
|
||||
"requested": "2023-08-23T15:59:24Z",
|
||||
"round": 1000,
|
||||
"effective": "2023-08-23T15:59:24Z"
|
||||
},
|
||||
{
|
||||
"name": "one second before the round 1000 boundary",
|
||||
"requested": "2023-08-23T15:59:23Z",
|
||||
"round": 1000,
|
||||
"effective": "2023-08-23T15:59:24Z"
|
||||
},
|
||||
{
|
||||
"name": "one second after the round 1000 boundary",
|
||||
"requested": "2023-08-23T15:59:25Z",
|
||||
"round": 1001,
|
||||
"effective": "2023-08-23T15:59:27Z"
|
||||
},
|
||||
{
|
||||
"name": "1ns after the round 1000 boundary",
|
||||
"requested": "2023-08-23T15:59:24.000000001Z",
|
||||
"round": 1001,
|
||||
"effective": "2023-08-23T15:59:27Z"
|
||||
},
|
||||
{
|
||||
"name": "half a second after the round 1000 boundary",
|
||||
"requested": "2023-08-23T15:59:24.5Z",
|
||||
"round": 1001,
|
||||
"effective": "2023-08-23T15:59:27Z"
|
||||
},
|
||||
{
|
||||
"name": "normative vector 2030-01-01 (spec §16)",
|
||||
"requested": "2030-01-01T00:00:00Z",
|
||||
"round": 66884212,
|
||||
"effective": "2030-01-01T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"name": "1ns after 2030-01-01",
|
||||
"requested": "2030-01-01T00:00:00.000000001Z",
|
||||
"round": 66884213,
|
||||
"effective": "2030-01-01T00:00:03Z"
|
||||
},
|
||||
{
|
||||
"name": "normative vector round 66432123 (spec §16)",
|
||||
"requested": "2029-12-16T07:15:33Z",
|
||||
"round": 66432123,
|
||||
"effective": "2029-12-16T07:15:33Z"
|
||||
},
|
||||
{
|
||||
"name": "offset timezone equals UTC instant",
|
||||
"requested": "2026-10-22T19:00:00.001+02:00",
|
||||
"round": 33295013,
|
||||
"effective": "2026-10-22T17:00:03Z"
|
||||
},
|
||||
{
|
||||
"name": "last representable round time",
|
||||
"requested": "9999-12-31T23:59:57Z",
|
||||
"round": 83903165811,
|
||||
"effective": "9999-12-31T23:59:57Z"
|
||||
},
|
||||
{
|
||||
"name": "after the last representable round",
|
||||
"requested": "9999-12-31T23:59:59Z",
|
||||
"error": "ERR_DATEKEY_INVALID"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2024", "DOM"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"noImplicitOverride": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"isolatedModules": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "tests/**/*.ts"]
|
||||
}
|
||||
Loading…
Reference in new issue