You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DateKeys/internal/testkit/points.go

100 lines
3.3 KiB

Spec v0.8.2 amendment: canonical point encoding; no library error text Amendment of the unreleased v0.8.2, recorded in §76 with its case: the second implementation's phase-2 research found that tlock-js over @noble/curves 1.9.7 accepts U re-encoded as c0 + p and a signature x + p and returns the same file key, while the reference rejects both (noble 1.9.7 differed from kilic on 5,615 of 41,686 encodings), and the spec did not say which encodings are valid. - §12.2 defines the canonical encoding of a BLS12-381 point (drand's compressed ZCash form) and requires decoders to reject every other byte string; §12.1 applies it to public_key. - §63 step 10 applies it to the release signature (ERR_RELEASE_INVALID) and step 11 defines the tlock stanza body U || V || W (96 + 16 + 16 bytes for Quicknet) with a canonical, non-infinity U (ERR_INTEGRITY). - §64 gains ten mutations, exported to mutations.json (65 cases). The signature x + p case uses published Quicknet round 1004, the first after 1000 whose x allows x + p < 2^381. The reference already gave every stated code and step. Errors no longer copy text from tlock, kyber, age, drand or kyber-bls12381. kyber's IBE error carried the candidate plaintext and r, and with one bit of W flipped the message disclosed the real tlock file key with that bit flipped. Every such place now uses a fixed reason with its normative sentinel; TestTlockFailureDiagnosticsCarryNoSecrets fails with the old wrapping. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
package testkit
import (
"bytes"
"errors"
"math/big"
)
// Re-encodings of BLS12-381 points in the compressed form of drand (spec
// §12.2), for the mutations and tests of the canonical point encoding. They
// work on the bytes alone: a flag byte whose low five bits start a
// big-endian coordinate, and coordinates of 48 bytes, x in G1 and c1 then c0
// in G2.
// FieldModulus is p, the modulus of the base field of BLS12-381.
var FieldModulus, _ = new(big.Int).SetString("1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", 16)
// Flags of the first byte of a compressed point (spec §12.2).
const (
FlagCompressed = 0x80
FlagInfinity = 0x40
FlagSort = 0x20
flagMask = FlagCompressed | FlagInfinity | FlagSort
)
// CoordinateLen is the size of a coordinate of Fp, and of a compressed point
// of G1; a compressed point of G2 takes two.
const CoordinateLen = 48
// AddModulus returns enc with p added to the 48-byte coordinate at byte
// offset at: 0 for x in G1 and for c1 in G2, 48 for c0 in G2. The flags are
// kept. The result reduces modulo p to the coordinate of enc, so a decoder
// that reduces coordinates reads the point of enc, but it is not a canonical
// encoding (spec §12.2). It fails when the sum does not fit in the bits of
// the coordinate: at offset 0, the 381 bits below the flags.
func AddModulus(enc []byte, at int) ([]byte, error) {
if at < 0 || at%CoordinateLen != 0 || at+CoordinateLen > len(enc) {
return nil, errors.New("testkit: no coordinate at that offset")
}
c := bytes.Clone(enc[at : at+CoordinateLen])
bits := 8 * CoordinateLen
if at == 0 {
c[0] &^= flagMask
bits -= 3
}
sum := new(big.Int).Add(new(big.Int).SetBytes(c), FieldModulus)
if sum.BitLen() > bits {
return nil, errors.New("testkit: the coordinate plus p does not fit")
}
out := bytes.Clone(enc)
sum.FillBytes(out[at : at+CoordinateLen])
if at == 0 {
out[0] |= enc[0] & flagMask
}
return out, nil
}
// ReduceCoordinate returns enc with the 48-byte coordinate at byte offset at
// reduced modulo p, the flags kept: what a decoder that reduces coordinates
// reads, and the inverse of AddModulus.
func ReduceCoordinate(enc []byte, at int) []byte {
out := bytes.Clone(enc)
c := out[at : at+CoordinateLen]
flags := byte(0)
if at == 0 {
flags = c[0] & flagMask
c[0] &^= flagMask
}
new(big.Int).Mod(new(big.Int).SetBytes(c), FieldModulus).FillBytes(c)
c[0] |= flags
return out
}
// Negated returns the encoding of the negated point: the same x, the sort
// flag flipped (spec §12.2). It is canonical when enc is the canonical
// encoding of a point other than the point at infinity.
func Negated(enc []byte) []byte {
out := bytes.Clone(enc)
out[0] ^= FlagSort
return out
}
// InfinityWithPayload returns enc with the flags of the point at infinity,
// compression and infinity without sort, over its own coordinate bits: an
// encoding of the point at infinity with a payload, which spec §12.2
// forbids.
func InfinityWithPayload(enc []byte) []byte {
out := bytes.Clone(enc)
out[0] = out[0]&^flagMask | FlagCompressed | FlagInfinity
return out
}
// Infinity returns the canonical encoding of the point at infinity in n
// bytes, 48 for G1 and 96 for G2: 0xc0, then zeros (spec §12.2).
func Infinity(n int) []byte {
out := make([]byte, n)
out[0] = FlagCompressed | FlagInfinity
return out
}

Powered by TurnKey Linux.