package capsule import ( "fmt" "math/bits" ) // Padding is the padding rule of the payload of a format 2 capsule, sealed in // key 7 of CONTROL_CBOR (spec §29.1, §31). No code stands for no padding. type Padding uint8 // Padding rules of format 2 (spec §29.1). const ( // Bloque256 pads the content to the next multiple of 256 bytes, and to // at least 256. Bloque256 Padding = 1 // Reforzado pads to the larger of Bloque256 and Padmé. It is the rule // Encrypt uses by default, as spec §29.1 asks of the official SDK. Reforzado Padding = 2 ) // MaxPayloadLength is L_MAX = 2^53 - 2^46, the largest content length a // format 2 capsule can seal: the largest L for which both rules give a P of // at most 2^53 - 1 (spec §29.1). const MaxPayloadLength = 1<<53 - 1<<46 func (p Padding) String() string { switch p { case Bloque256: return "bloque256" case Reforzado: return "reforzado" } return fmt.Sprintf("padding(%d)", uint8(p)) } func (p Padding) valid() bool { return p == Bloque256 || p == Reforzado } // PaddedLength returns P = rule(L), the exact length of the plaintext of // PAYLOAD_AGE in a format 2 capsule whose content is l bytes long (spec // §29.1). The arithmetic is exact and on 64-bit integers only, never a // floating-point logarithm or 32-bit operations. func PaddedLength(l uint64, p Padding) (uint64, error) { if !p.valid() { return 0, fmt.Errorf("capsule: padding code %d is not defined", uint8(p)) } if l > MaxPayloadLength { return 0, fmt.Errorf("capsule: content of %d bytes exceeds L_MAX = %d", l, uint64(MaxPayloadLength)) } if l <= 256 { return 256, nil } block := (l + 255) &^ 255 if p == Bloque256 { return block, nil } // Padmé: keep the S + 1 most significant bits of L and round up the // others, with E = floor(log2 L) and S = floor(log2 E) + 1. e := uint64(bits.Len64(l) - 1) s := uint64(bits.Len64(e)) mask := uint64(1)<<(e-s) - 1 return max(block, (l+mask)&^mask), nil } // PayloadAgeLength returns the length of a PAYLOAD_AGE whose plaintext is n // bytes long: the 184 bytes of an age header with one X25519 stanza and the // nonce, the plaintext, and the 16-byte tag of each 64 KiB STREAM chunk, at // least one (spec §62.1, informative note). func PayloadAgeLength(n uint64) uint64 { chunks := max(1, (n+65535)/65536) return 184 + n + 16*chunks }