package testkit import ( "fmt" "math/big" "g.activething.com/go/DateKeys/capsule" ) // PaddingVectorFile is testdata/vectors/padding.json (spec §29.1, §67). type PaddingVectorFile struct { Spec string `json:"spec"` Description string `json:"description"` // LMax is L_MAX = 2^53 - 2^46, the largest L a format 2 capsule seals. LMax uint64 `json:"l_max"` Vectors []PaddingVector `json:"vectors"` // Rejected are lengths above L_MAX: a writer does not seal them and a // reader rejects them in CONTROL_CBOR (spec §31). Rejected []uint64 `json:"rejected"` } // PaddingVector is P = rule(L) for both rules, and the length of a // PAYLOAD_AGE with that plaintext, 184 + P + 16·max(1, ⌈P / 65536⌉). Every // value is at most 2^53 - 1, exact as a JSON number. type PaddingVector struct { L uint64 `json:"l"` Bloque256 uint64 `json:"bloque256"` Reforzado uint64 `json:"reforzado"` PayloadAgeBloque256 uint64 `json:"payload_age_bloque256"` PayloadAgeReforzado uint64 `json:"payload_age_reforzado"` // E, S and LastBits are informative: the intermediate values of Padmé, // only for L > 256, where it is used. E *uint64 `json:"e,omitempty"` S *uint64 `json:"s,omitempty"` LastBits *uint64 `json:"last_bits,omitempty"` } // paddingLengths are the rows of the table of spec §29.1 and of the worked // examples of the v0.9 design note, plus 2^49 - 1, the first L at which a // floating-point log2 gives a wrong E. var paddingLengths = []uint64{ 0, 1, 40, 255, 256, 257, 1000, 4096, 8192, 8193, 10000, 65536, 65537, 78000, 1000000, 3000000, 600000000, 1000000000, 2113929216, 2113929217, // the first failure of 32-bit signed operators 4227858432, 4227858433, // the first failure of 32-bit operators with >>> 0 1<<32 - 1, 1<<32 + 1, 5000000000, 1000000000000, 1<<49 - 1, 1<<52 + 1, capsule.MaxPayloadLength, } // PaddingVectors computes testdata/vectors/padding.json with the // implementation, and checks every value against an independent Padmé // computed with math/big. func PaddingVectors() (PaddingVectorFile, error) { f := PaddingVectorFile{ Spec: SpecVersion, Description: "Padding rules of the payload of a format 2 capsule (spec §29.1): for each content length L, P with code 1 (bloque256) and code 2 (reforzado), " + "and the length of PAYLOAD_AGE for each. e, s and last_bits are informative. Generated by the reference implementation. See testdata/README.md.", LMax: capsule.MaxPayloadLength, Rejected: []uint64{capsule.MaxPayloadLength + 1, 1 << 53}, } for _, l := range paddingLengths { v := PaddingVector{L: l} var err error if v.Bloque256, err = capsule.PaddedLength(l, capsule.Bloque256); err != nil { return f, err } if v.Reforzado, err = capsule.PaddedLength(l, capsule.Reforzado); err != nil { return f, err } b, r := bigPadding(l) if v.Bloque256 != b || v.Reforzado != r { return f, fmt.Errorf("testkit: L = %d: P is %d and %d, math/big gives %d and %d", l, v.Bloque256, v.Reforzado, b, r) } v.PayloadAgeBloque256 = capsule.PayloadAgeLength(v.Bloque256) v.PayloadAgeReforzado = capsule.PayloadAgeLength(v.Reforzado) if l > 256 { e := uint64(big.NewInt(0).SetUint64(l).BitLen() - 1) s := uint64(big.NewInt(0).SetUint64(e).BitLen()) last := e - s v.E, v.S, v.LastBits = &e, &s, &last } f.Vectors = append(f.Vectors, v) } for _, l := range f.Rejected { for _, code := range []capsule.Padding{capsule.Bloque256, capsule.Reforzado} { if _, err := capsule.PaddedLength(l, code); err == nil { return f, fmt.Errorf("testkit: L = %d above L_MAX is accepted", l) } } } return f, nil } // bigPadding computes bloque256 and reforzado of l with math/big, following // the definition of spec §29.1 literally. func bigPadding(l uint64) (bloque256, reforzado uint64) { if l <= 256 { return 256, 256 } L := new(big.Int).SetUint64(l) b := new(big.Int).Add(L, big.NewInt(255)) b.Rsh(b, 8).Lsh(b, 8) e := L.BitLen() - 1 s := big.NewInt(int64(e)).BitLen() mask := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), uint(e-s)), big.NewInt(1)) padme := new(big.Int).Add(L, mask) padme.AndNot(padme, mask) r := b if padme.Cmp(b) > 0 { r = padme } return b.Uint64(), r.Uint64() }