Spec v0.14 draft: one scheme of drand, bls-unchained-g1-rfc9380

The author approved the draft with its recommendations, decision 8 among
them: section 12.1 admits only the scheme whose verification and tlock
decryption the text now writes byte for byte. profile.Validate refuses the
other two unchained schemes of drand with ERR_UNKNOWN_PROFILE. No pinned
profile and no vector changes; section 76 records it as change 7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.14
dev 1 day ago
parent 49b376411e
commit c041fa37ad

@ -15,6 +15,11 @@ path: `SpecVersion` stays 0.13 until the author approves the draft.
§71), signatures and seals against a quantum adversary (§7.7, §53), the web §71), signatures and seals against a quantum adversary (§7.7, §53), the web
client (§7.10, §59), the entropy of a key of words (§38.1), the root of client (§7.10, §59), the entropy of a key of words (§38.1), the root of
trust byte for byte (§12, §35, §51, §63 steps 10 and 11), and errata. trust byte for byte (§12, §35, §51, §63 steps 10 and 11), and errata.
- **One scheme of drand** (§12.1, §76 change 7, decision 8 of the author,
6 October 2026). `profile.Validate` admits only `bls-unchained-g1-rfc9380`:
a profile of `pedersen-bls-unchained` or `bls-unchained-on-g1` is
`ERR_UNKNOWN_PROFILE`. No pinned profile changes, and no vector of
`testdata`.
- **Test data.** `vectors/tlock_steps.json`, new, from - **Test data.** `vectors/tlock_steps.json`, new, from
`internal/testkit.TlockStepVectors`: for the published rounds 1000, 1001, `internal/testkit.TlockStepVectors`: for the published rounds 1000, 1001,
1004 and 2000, the message a round signs, its hash to G1 and the pairing 1004 and 2000, the message a round signs, its hash to G1 and the pairing

@ -58,10 +58,11 @@ func TestDecodePrecedence(t *testing.T) {
{"public key not a point and chain hash", func(c map[uint64]any) { c[6] = bytes.Repeat([]byte{0xff}, 96); chain(c) }, datekeys.ErrUnknownProfile}, {"public key not a point and chain hash", func(c map[uint64]any) { c[6] = bytes.Repeat([]byte{0xff}, 96); chain(c) }, datekeys.ErrUnknownProfile},
{"public key the identity and chain hash", func(c map[uint64]any) { k := make([]byte, 96); k[0] = 0xc0; c[6] = k; chain(c) }, datekeys.ErrUnknownProfile}, {"public key the identity and chain hash", func(c map[uint64]any) { k := make([]byte, 96); k[0] = 0xc0; c[6] = k; chain(c) }, datekeys.ErrUnknownProfile},
// Spec §12.1 rule 2: the key is a point of the prime-order subgroup. // Spec §12.1 rule 2: the key is a point of the prime-order subgroup.
// pedersen-bls-unchained has its keys on G1 (48 bytes); its generator // Since spec v0.14, rule 2 admits only bls-unchained-g1-rfc9380: a
// passes rule 2 and only the chain hash fails, while a point of the // profile of the other schemes of drand fails rule 2 before its chain
// curve outside the subgroup fails rule 2. // hash, with a valid key of its own group or not.
{"G1 generator and chain hash", func(c map[uint64]any) { c[9], c[6] = "pedersen-bls-unchained", g1Generator(t); chain(c) }, datekeys.ErrProfileMismatch}, {"G1 generator and chain hash", func(c map[uint64]any) { c[9], c[6] = "pedersen-bls-unchained", g1Generator(t); chain(c) }, datekeys.ErrUnknownProfile},
{"the scheme bls-unchained-on-g1 and chain hash", func(c map[uint64]any) { c[9] = "bls-unchained-on-g1"; chain(c) }, datekeys.ErrUnknownProfile},
{"G1 point outside the prime-order subgroup and chain hash", func(c map[uint64]any) { c[9], c[6] = "pedersen-bls-unchained", offSubgroupG1(t); chain(c) }, datekeys.ErrUnknownProfile}, {"G1 point outside the prime-order subgroup and chain hash", func(c map[uint64]any) { c[9], c[6] = "pedersen-bls-unchained", offSubgroupG1(t); chain(c) }, datekeys.ErrUnknownProfile},
{"chain hash alone", chain, datekeys.ErrProfileMismatch}, {"chain hash alone", chain, datekeys.ErrProfileMismatch},
} { } {

@ -285,10 +285,10 @@ func (p *Profile) validateDrand() error {
if err != nil { if err != nil {
return err return err
} }
switch scheme.Name { // Spec v0.14, §12.1: the one scheme whose release and tlock decryption
case crypto.SigsOnG1ID, crypto.UnchainedSchemeID, crypto.ShortSigSchemeID: // the specification writes byte for byte.
default: if scheme.Name != crypto.SigsOnG1ID {
return fmt.Errorf("profile %s: scheme %q is not supported by tlock: %w", p.ID, scheme.Name, datekeys.ErrUnknownProfile) return fmt.Errorf("profile %s: scheme %q is not %s, the only scheme of V1: %w", p.ID, scheme.Name, crypto.SigsOnG1ID, datekeys.ErrUnknownProfile)
} }
key := scheme.KeyGroup.Point() key := scheme.KeyGroup.Point()
if err := key.UnmarshalBinary(p.PublicKey); err != nil { if err := key.UnmarshalBinary(p.PublicKey); err != nil {

@ -385,8 +385,8 @@ Una implementación MUST aplicar estas reglas antes de pinnear un Provider Profi
- `period` (clave 7) es como mucho 2³² − 1 (4294967295) segundos, porque el hash del punto 3 lo codifica en 32 bits; - `period` (clave 7) es como mucho 2³² − 1 (4294967295) segundos, porque el hash del punto 3 lo codifica en 32 bits;
- `genesis_time` (clave 8) está entre 1 y 253402300798 (9999-12-31T23:59:58Z): posterior al instante 0 y anterior al último instante representable de §15; - `genesis_time` (clave 8) está entre 1 y 253402300798 (9999-12-31T23:59:58Z): posterior al instante 0 y anterior al último instante representable de §15;
- `provider` es `drand`, el único proveedor de V1; - `provider` es `drand`, el único proveedor de V1;
- `scheme` es uno de los schemes drand sin encadenar que tlock admite: `pedersen-bls-unchained`, `bls-unchained-on-g1` o `bls-unchained-g1-rfc9380`; - `scheme` es `bls-unchained-g1-rfc9380`, el único scheme de drand cuya verificación y cuyo descifrado tlock fija este documento byte a byte (§63, pasos 10 y 11). Los otros schemes sin encadenar que tlock admite, `pedersen-bls-unchained` y `bls-unchained-on-g1`, no son de V1;
- `public_key` es la codificación canónica (§12.2) de un punto del grupo de claves del scheme —G1, 48 bytes, para `pedersen-bls-unchained`; G2, 96 bytes, para los otros dos— distinto del punto en el infinito. - `public_key` es la codificación canónica (§12.2) de un punto de G2, 96 bytes, distinto del punto en el infinito.
3. **Autocomprobación de `chain_hash`.** Relaciona varias claves y por eso va después de todas las comprobaciones de campo. `chain_hash` (clave 5) MUST ser el hash de la información de cadena de drand (drand Protocol Specification, sección *Root of trust*, §77) de los demás parámetros: 3. **Autocomprobación de `chain_hash`.** Relaciona varias claves y por eso va después de todas las comprobaciones de campo. `chain_hash` (clave 5) MUST ser el hash de la información de cadena de drand (drand Protocol Specification, sección *Root of trust*, §77) de los demás parámetros:
```text ```text
@ -2950,9 +2950,7 @@ Sin extensiones de control, C = 103: en la ronda 1000, `SEALED_CONTROL_LEN` vale
El cuerpo del stanza tlock es el ciphertext IBE-CCA con el que tlock El cuerpo del stanza tlock es el ciphertext IBE-CCA con el que tlock
(drand/tlock, §77) envuelve la file key para la ronda: (drand/tlock, §77) envuelve la file key para la ronda:
U || V || W U || V || W
con |U| el tamaño de punto del grupo de claves del scheme —96 bytes, con |U| = 96, un punto de G2, y |V| = |W| = 16: 128 bytes. U MUST ser la codificación
G2, en Quicknet; 48, G1, en pedersen-bls-unchained— y
|V| = |W| = 16: 128 bytes en Quicknet. U MUST ser la codificación
canónica (§12.2) de un punto de ese grupo distinto del punto en el canónica (§12.2) de un punto de ese grupo distinto del punto en el
infinito. El descifrado es el de tlock con la firma verificada en el infinito. El descifrado es el de tlock con la firma verificada en el
paso 10; en Quicknet, con e el pairing de G1 × G2: paso 10; en Quicknet, con e el pairing de G1 × G2:
@ -3558,7 +3556,7 @@ Las versiones que definan la firma de autor y el sello de tiempo no cambiarán l
Un lector de la v0.10 abre las cápsulas de un escritor de la v0.11: acepta el área de 32 KiB y la de 64 KiB (§29.2), da F1 a `alg` 1 y 2 y S1 a `seal_type` 2 (§29.7), e ignora la nota pública y la extensión `datekeys.capsule` (§54). La llave de palabras es un recipient X25519 más, así que la identity que se deriva abre la cápsula en cualquier lector (§38.1). Un lector de la v0.11 abre las cápsulas de la v0.10, con su área de 512 bytes. Un lector de la v0.10 abre las cápsulas de un escritor de la v0.11: acepta el área de 32 KiB y la de 64 KiB (§29.2), da F1 a `alg` 1 y 2 y S1 a `seal_type` 2 (§29.7), e ignora la nota pública y la extensión `datekeys.capsule` (§54). La llave de palabras es un recipient X25519 más, así que la identity que se deriva abre la cápsula en cualquier lector (§38.1). Un lector de la v0.11 abre las cápsulas de la v0.10, con su área de 512 bytes.
La v0.14 no cambia ningún formato ni ningún veredicto. La v0.14 no cambia ningún formato ni ningún veredicto de una cápsula de Quicknet. Un Provider Profile de un scheme de drand distinto de `bls-unchained-g1-rfc9380` deja de pasar §12.1, con `ERR_UNKNOWN_PROFILE`: V1 no pinnea ninguno.
La v0.13 no cambia ningún formato ni ningún veredicto: solo deja a un lector descargar el resto de un sobre desde una red solo IPv6 con NAT64 (§44.1). La v0.13 no cambia ningún formato ni ningún veredicto: solo deja a un lector descargar el resto de un sobre desde una red solo IPv6 con NAT64 (§44.1).
@ -4259,7 +4257,7 @@ La v0.13 recoge lo que encontró la implementación del localizador en Dart, el
### Cambios normativos de la v0.14 ### Cambios normativos de la v0.14
La v0.14 escribe lo que la revisión de completitud del 6 de octubre de 2026 encontró sin escribir, de cara a la v1.0, y la raíz de confianza byte a byte, como ya la aplican las tres implementaciones. No cambia ningún formato ni ningún veredicto (§70). La v0.14 escribe lo que la revisión de completitud del 6 de octubre de 2026 encontró sin escribir, de cara a la v1.0, y la raíz de confianza byte a byte, como ya la aplican las tres implementaciones. No cambia ningún formato ni ningún veredicto de una cápsula de Quicknet; solo deja de admitir un Provider Profile de otro scheme de drand (§70).
1. **Lo que no se garantiza** (§5). 1. **Lo que no se garantiza** (§5).
- Cambio: §5 dice que nadie puede comprobar antes de la fecha que una cápsula se abrirá, y añade la caducidad, las condiciones distintas de una ronda y la cancelación. - Cambio: §5 dice que nadie puede comprobar antes de la fecha que una cápsula se abrirá, y añade la caducidad, las condiciones distintas de una ronda y la cancelación.
@ -4291,7 +4289,12 @@ La v0.14 escribe lo que la revisión de completitud del 6 de octubre de 2026 enc
- Motivo: el paso 10 verificaba «según el scheme de drand», y H3 y H4 eran «las de drand/kyber»: verificar un release o abrir un stanza tlock exigía leer drand, kyber y kilic (revisión de completitud de la v0.13, punto 2.5). - Motivo: el paso 10 verificaba «según el scheme de drand», y H3 y H4 eran «las de drand/kyber»: verificar un release o abrir un stanza tlock exigía leer drand, kyber y kilic (revisión de completitud de la v0.13, punto 2.5).
- Caso: H3 desplaza un bit a la derecha el primer byte de cada hash; una implementación que lo lee como una máscara y pone a cero el bit más significativo obtiene otro r, y la comprobación r·G2 == U falla en todas las cápsulas. En el stanza 111 de la ronda 1000 de `tlock_steps.json`, esa regla acepta el segundo intento en vez del cuarto. Con el DST de G2 sobre G1, el de `bls-unchained-on-g1`, o con la ronda sin SHA-256 como mensaje, ninguna firma de Quicknet verifica. - Caso: H3 desplaza un bit a la derecha el primer byte de cada hash; una implementación que lo lee como una máscara y pone a cero el bit más significativo obtiene otro r, y la comprobación r·G2 == U falla en todas las cápsulas. En el stanza 111 de la ronda 1000 de `tlock_steps.json`, esa regla acepta el segundo intento en vez del cuarto. Con el DST de G2 sobre G1, el de `bls-unchained-on-g1`, o con la ronda sin SHA-256 como mensaje, ninguna firma de Quicknet verifica.
- Pruebas previstas: `tlock_steps.json` (§64), nuevo. - Pruebas previstas: `tlock_steps.json` (§64), nuevo.
7. **Erratas** (§50, §76). 7. **Un solo scheme de drand** (§12.1).
- Cambio: un Provider Profile V1 solo admite el scheme `bls-unchained-g1-rfc9380`, con la clave pública en G2; `pedersen-bls-unchained` y `bls-unchained-on-g1` dan `ERR_UNKNOWN_PROFILE` en el punto 2 de §12.1.
- Motivo: con el cambio 6, el texto fija byte a byte la verificación y el descifrado de ese scheme y de ningún otro; y las implementaciones divergían: la de referencia verificaba los tres, y las de TypeScript y Dart, solo el de Quicknet. V1 pinnea solo Quicknet, así que ninguna cápsula válida cambia.
- Caso: un perfil de `bls-unchained-on-g1` con su clave y su `chain_hash` válidos pasaba §12.1 y la referencia verificaba sus releases con el DST de G2 sobre G1, mientras que TypeScript y Dart los rechazaban en el paso 10.
- Pruebas previstas: `profile.TestDecodePrecedence`, con un perfil de cada uno de los otros dos schemes.
8. **Erratas** (§50, §76).
- Cambio: §50 recuerda que el `.dkc` y el resto de un sobre son la misma dependencia de largo plazo que el release; el título de §76 ya no dice «del borrador v0.8.1»; y los cambios de cada versión van en orden. - Cambio: §50 recuerda que el `.dkc` y el resto de un sobre son la misma dependencia de largo plazo que el release; el título de §76 ya no dice «del borrador v0.8.1»; y los cambios de cada versión van en orden.
- Motivo: el bloque de la v0.13 iba antes que el de la v0.12. - Motivo: el bloque de la v0.13 iba antes que el de la v0.12.
- Pruebas previstas: ninguna. - Pruebas previstas: ninguna.

Loading…
Cancel
Save

Powered by TurnKey Linux.