mirror of
https://github.com/vlang/v.git
synced 2025-09-14 23:12:33 +03:00
376 lines
12 KiB
V
376 lines
12 KiB
V
// Copyright (c) 2024 blackshirt.
|
|
// Use of this source code is governed by an MIT license
|
|
// that can be found in the LICENSE file.
|
|
//
|
|
// Poly1305 one-time message authentication code (MAC) module
|
|
module poly1305
|
|
|
|
import math
|
|
import math.unsigned
|
|
import encoding.binary
|
|
import crypto.internal.subtle
|
|
|
|
// Constants defined in this module
|
|
// -------------------------------
|
|
// block_size is the internal size of the Poly1305 block that is operates on
|
|
const block_size = 16
|
|
// key_size is a 256-bit one-time key size for input to Poly1305 mac in bytes.
|
|
const key_size = 32
|
|
// tag_size is the size of the output of the Poly1305 result, in bytes.
|
|
const tag_size = 16
|
|
|
|
// mask value for clamping low 64 bits of the r part, clearing 10 bits
|
|
const rmask0 = u64(0x0FFF_FFFC_0FFF_FFFF)
|
|
const not_rmask0 = ~rmask0
|
|
// mask value for clamping high 64 bits of the r part, clearing 12 bits
|
|
const rmask1 = u64(0x0FFF_FFFC_0FFF_FFFC)
|
|
const not_rmask1 = ~rmask1
|
|
|
|
// mask value for low 2 bits of u64 value
|
|
const mask_low2bits = u64(0x0000_0000_0000_0003)
|
|
// mask value for high 62 bits of u64 value
|
|
const mask_high62bits = u64(0xFFFF_FFFF_FFFF_FFFC)
|
|
// mask value for high 60 bits of u64 value
|
|
const mask_high60bits = u64(0xFFFF_FFFF_FFFF_FFF0)
|
|
|
|
// p is 130 bit of Poly1305 constant prime, ie 2^130-5
|
|
// as defined in rfc, p = 3fffffffffffffffffffffffffffffffb
|
|
const p = Uint192{
|
|
lo: u64(0xFFFF_FFFF_FFFF_FFFB)
|
|
mi: u64(0xFFFF_FFFF_FFFF_FFFF)
|
|
hi: u64(0x0000_0000_0000_0003)
|
|
}
|
|
|
|
// Poly1305 mac instance
|
|
struct Poly1305 {
|
|
mut:
|
|
// Poly1305 mac accepts 32 bytes (256 bits) of key input.
|
|
// This key is partitioned into two's 128 bits parts, r and s
|
|
// where r is clamped before stored and the s part is kept secret.
|
|
r unsigned.Uint128
|
|
s unsigned.Uint128
|
|
// Poly1305 accumulator
|
|
h Uint192
|
|
// buffer
|
|
buffer []u8 = []u8{len: poly1305.block_size}
|
|
leftover int
|
|
// The done flag tells us if the instance should not be used again.
|
|
// It's set to true after calling finish or reset on the instance.
|
|
done bool
|
|
}
|
|
|
|
// create_tag generates 16 bytes tag, i.e., a one-time message authenticator code (mac) stored into out.
|
|
// It accepts the message bytes to be authenticated and the 32 bytes of the key.
|
|
// This is an oneshot function to create a tag and reset internal state after the call.
|
|
// For incremental updates, use the method based on Poly1305 mac instance.
|
|
pub fn create_tag(mut out []u8, msg []u8, key []u8) ! {
|
|
if out.len != poly1305.tag_size {
|
|
return error('poly1305: bad out tag_size')
|
|
}
|
|
mut po := new(key)!
|
|
po.update(msg)
|
|
po.finish(mut out)
|
|
}
|
|
|
|
// verify_tag verifies the tag is a valid message authentication code for the msg
|
|
// compared to the tag output from the calculated process.
|
|
// It returns `true` if two tags is matching, `false` otherwise.
|
|
pub fn verify_tag(tag []u8, msg []u8, key []u8) bool {
|
|
mut po := new(key) or { panic(err) }
|
|
mut out := []u8{len: poly1305.tag_size}
|
|
po.update(msg)
|
|
po.finish(mut out)
|
|
return subtle.constant_time_compare(tag, out) == 1
|
|
}
|
|
|
|
// new creates a new Poly1305 mac instance from 32 bytes of key provided.
|
|
@[direct_array_access]
|
|
pub fn new(key []u8) !&Poly1305 {
|
|
if key.len != poly1305.key_size {
|
|
return error('poly1305: bad key length')
|
|
}
|
|
// Read the r part of the key and clamp it. Clamping was done by clearing
|
|
// some bits of r before being used. The spec says the bits from 16 bytes of r,
|
|
// that are required to be clamped are: some odd index bytes, i.e., r[3],
|
|
// r[7], r[11], and r[15], are required to have their top four bits clear
|
|
// (be smaller than 16), and some even index bytes, i.e., r[4], r[8], and r[12],
|
|
// are required to have their bottom two bits clear (be divisible by 4),
|
|
// totally clearing 22 bits. In 128-bit little-endian format, the clamping
|
|
// mask value is 0x0ffffffc0ffffffc0ffffffc0fffffff.
|
|
// See the rmask0 and rmask1 constants above.
|
|
r := unsigned.Uint128{
|
|
lo: binary.little_endian_u64(key[0..8]) & poly1305.rmask0
|
|
hi: binary.little_endian_u64(key[8..16]) & poly1305.rmask1
|
|
}
|
|
|
|
// read s part from the rest bytes of key
|
|
s := unsigned.Uint128{
|
|
lo: binary.little_endian_u64(key[16..24])
|
|
hi: binary.little_endian_u64(key[24..32])
|
|
}
|
|
|
|
ctx := &Poly1305{
|
|
r: r
|
|
s: s
|
|
}
|
|
return ctx
|
|
}
|
|
|
|
// update updates internal of Poly1305 state by message.
|
|
pub fn (mut po Poly1305) update(msg []u8) {
|
|
poly1305_update_block(mut po, msg)
|
|
}
|
|
|
|
// verify verifies if the `tag` is a valid message authenticated code for current state of
|
|
// Poly1305 instance. Internally, it works on clone of the current instance.
|
|
pub fn (po Poly1305) verify(tag []u8) bool {
|
|
assert tag.len == poly1305.tag_size
|
|
// we work on copy of current instance
|
|
mut ctx := po
|
|
mut out := []u8{len: poly1305.tag_size}
|
|
if ctx.leftover > 0 {
|
|
poly1305_blocks(mut ctx, ctx.buffer[..ctx.leftover])
|
|
}
|
|
finalize(mut out, mut ctx.h, ctx.s)
|
|
return subtle.constant_time_compare(tag[..], out) == 1
|
|
}
|
|
|
|
// finish finalizes the message authentication code calculation and stores the result into out.
|
|
// After calls this method, don't use the instance anymore to do most anything, but,
|
|
// you should reinitialize the instance with the new key with .`reinit` method instead.
|
|
pub fn (mut po Poly1305) finish(mut out []u8) {
|
|
if po.done {
|
|
panic('poly1305: has done, please reinit with the new key')
|
|
}
|
|
if po.leftover > 0 {
|
|
poly1305_blocks(mut po, po.buffer[..po.leftover])
|
|
}
|
|
finalize(mut out, mut po.h, po.s)
|
|
// we reset instance to make its in bad unusable state.
|
|
po.reset()
|
|
}
|
|
|
|
// reinit reinitializes Poly1305 mac instance by resetting internal fields, and
|
|
// then reinit instance with the new key.
|
|
pub fn (mut po Poly1305) reinit(key []u8) {
|
|
if key.len != poly1305.key_size {
|
|
panic('bad key size')
|
|
}
|
|
// first, we reset the instance and than setup its again
|
|
po.reset()
|
|
po.r = unsigned.Uint128{
|
|
lo: binary.little_endian_u64(key[0..8]) & poly1305.rmask0
|
|
hi: binary.little_endian_u64(key[8..16]) & poly1305.rmask1
|
|
}
|
|
po.s = unsigned.Uint128{
|
|
lo: binary.little_endian_u64(key[16..24])
|
|
hi: binary.little_endian_u64(key[24..32])
|
|
}
|
|
// we set po.done to false, to make its usable again.
|
|
po.done = false
|
|
}
|
|
|
|
// poly1305_update_block updates the internals of Poly105 state instance by block of message.
|
|
fn poly1305_update_block(mut po Poly1305, msg []u8) {
|
|
if msg.len == 0 {
|
|
return
|
|
}
|
|
if po.done {
|
|
panic('poly1305: has done, please reinit with the new key')
|
|
}
|
|
|
|
mut msglen := msg.len
|
|
mut idx := 0
|
|
// handle leftover
|
|
if po.leftover > 0 {
|
|
want := math.min(poly1305.block_size - po.leftover, msglen)
|
|
block := msg[idx..idx + want]
|
|
_ := copy(mut po.buffer[po.leftover..], block)
|
|
|
|
msglen -= want
|
|
idx += want
|
|
po.leftover += want
|
|
|
|
if po.leftover < poly1305.block_size {
|
|
return
|
|
}
|
|
poly1305_blocks(mut po, po.buffer)
|
|
po.leftover = 0
|
|
}
|
|
// process full blocks
|
|
if msglen >= poly1305.block_size {
|
|
want := (msglen & ~(poly1305.block_size - 1))
|
|
mut block := unsafe { msg[idx..idx + want] }
|
|
poly1305_blocks(mut po, block)
|
|
idx += want
|
|
msglen -= want
|
|
}
|
|
// store leftover
|
|
if msglen > 0 {
|
|
po.leftover += copy(mut po.buffer[po.leftover..], msg[idx..])
|
|
}
|
|
}
|
|
|
|
// reset zeroes the Poly1305 mac instance and puts it in an unusable state.
|
|
// You should reinit the instance with the new key instead to make it usable again.
|
|
fn (mut po Poly1305) reset() {
|
|
po.r = unsigned.uint128_zero
|
|
po.s = unsigned.uint128_zero
|
|
po.h = uint192_zero
|
|
po.leftover = 0
|
|
unsafe {
|
|
po.buffer.reset()
|
|
}
|
|
// We set the done flag to true to prevent accidental calls
|
|
// to update or finish methods on the instance.
|
|
po.done = true
|
|
}
|
|
|
|
// poly1305_blocks updates internal state of Poly1305 instance `po` with message `msg`
|
|
fn poly1305_blocks(mut po Poly1305, msg []u8) {
|
|
// nothing to do, just return
|
|
if msg.len == 0 {
|
|
return
|
|
}
|
|
// For correctness and clarity, we check whether r is properly clamped.
|
|
if po.r.lo & poly1305.not_rmask0 != 0 && po.r.hi & poly1305.not_rmask1 != 0 {
|
|
panic('poly1305: bad unclamped of r')
|
|
}
|
|
// We need the accumulator to be in correctly reduced form to make sure it is not overflowing.
|
|
// To be safe when used, only maximum of four low bits of the high part of the accumulator (h.hi)
|
|
// can be set, and the remaining high bits must not be set.
|
|
if po.h.hi & poly1305.mask_high60bits != 0 {
|
|
panic('poly1305: h need to be reduced')
|
|
}
|
|
|
|
// localize the thing
|
|
mut h := po.h
|
|
mut t := [4]u64{}
|
|
|
|
// The main routine for updating internal poly1305 state with blocks of messages done with step:
|
|
// - chop messages into 16-byte blocks and read block as little-endian number;
|
|
// - add one bit beyond the number (its dependz on the size of the block);
|
|
// - add this number to the accumulator and then multiply the accumulator by "r".
|
|
// - perform partial reduction modulo p on the result by calling `poly1305_squeeze` function.
|
|
// - updates poly1305 accumulator with the new values
|
|
mut msglen := msg.len
|
|
mut idx := 0
|
|
|
|
for msglen > 0 {
|
|
// carry
|
|
mut c := u64(0)
|
|
if msglen >= poly1305.block_size {
|
|
// Read the 16 bytes msg block as a little-endian number
|
|
// and stored into the 128 bits of Uint128
|
|
block := msg[idx..idx + poly1305.block_size]
|
|
m := unsigned.Uint128{
|
|
lo: binary.little_endian_u64(block[0..8])
|
|
hi: binary.little_endian_u64(block[8..16])
|
|
}
|
|
// add msg block to accumulator, h += m
|
|
h, c = h.add_128_checked(m, 0)
|
|
|
|
// // The rfc requires us to set a bit just above the message size, ie,
|
|
// add one bit beyond the number of octets. For a 16-byte block,
|
|
// this is equivalent to adding 2^128 to the number.
|
|
// so we can just add 1 to the high part of accumulator (h.hi += 1)
|
|
// h.hi has been checked above, so, its safe to assume its not overflow
|
|
h.hi += c + 1
|
|
idx += poly1305.block_size
|
|
msglen -= poly1305.block_size
|
|
} else {
|
|
// The last one msg block might be shorter than 16 bytes long,
|
|
// pad it with zeros to align with block_size.
|
|
mut buf := []u8{len: poly1305.block_size}
|
|
subtle.constant_time_copy(1, mut buf[..msglen], msg[idx..idx + msglen])
|
|
|
|
// set a bit above msg size.
|
|
buf[msglen] = u8(0x01)
|
|
// loads 16 bytes of message block
|
|
m := unsigned.Uint128{
|
|
lo: binary.little_endian_u64(buf[0..8])
|
|
hi: binary.little_endian_u64(buf[8..16])
|
|
}
|
|
|
|
// add this number to the accumulator, ie, h += m
|
|
h, c = h.add_128_checked(m, 0)
|
|
h.hi += c
|
|
idx += poly1305.block_size
|
|
msglen -= poly1305.block_size
|
|
}
|
|
|
|
// perform h *= r and then do partial reduction modulo p to the output.
|
|
mul_h_by_r(mut t, mut h, po.r)
|
|
poly1305_squeeze(mut h, t)
|
|
}
|
|
|
|
// updates internal accumulator
|
|
po.h = h
|
|
}
|
|
|
|
// finalize finalizes the reduction of accumulator h, adds it with secret s,
|
|
// and then take 128 bits of h stored into out.
|
|
fn finalize(mut out []u8, mut ac Uint192, s unsigned.Uint128) {
|
|
assert out.len == poly1305.tag_size
|
|
mut h := ac
|
|
// compute t = h - p = h - (2¹³⁰ - 5), and select h as the result if the
|
|
// subtraction underflows, and t otherwise.
|
|
t, b := h.sub_checked(poly1305.p)
|
|
|
|
// h = h if h < p else h - p
|
|
h.lo = select_64(b, h.lo, t.lo)
|
|
h.mi = select_64(b, h.mi, t.mi)
|
|
|
|
// Finally, we compute tag = h + s mod 2¹²⁸
|
|
// s is 128 bit of po.s, ie, Uint128
|
|
tag, _ := h.add_128_checked(s, 0)
|
|
|
|
// take only low 128 bit of x
|
|
binary.little_endian_put_u64(mut out[0..8], tag.lo)
|
|
binary.little_endian_put_u64(mut out[8..16], tag.mi)
|
|
}
|
|
|
|
// mul_h_by_r multiplies accumulator h by r and stores the result into four of the 64 bit limbs
|
|
fn mul_h_by_r(mut t [4]u64, mut h Uint192, r unsigned.Uint128) {
|
|
// Let's multiply h by r, h *= r, and stores the result into raw 320 bits of xh and hb
|
|
// In properly clamped r and reduced h, hb.hi bits should not be set, ie, hb.hi == 0
|
|
// see comments on mul_128_checked for details.
|
|
xh, hb := h.mul_128_checked(r)
|
|
|
|
// check for high bits of the result is not overflowing 256 bits, so we can ignore
|
|
// high bit (hb.hi) of the Uint128 part, fifth 64 bits limb.
|
|
if hb.hi != 0 {
|
|
panic('poly1305: unexpected overflow, non-null 5th limb')
|
|
}
|
|
|
|
// updates 4 64-bit limbs and ignore 5th limb
|
|
t[0] = xh.lo
|
|
t[1] = xh.mi
|
|
t[2] = xh.hi
|
|
t[3] = hb.lo
|
|
}
|
|
|
|
// poly1305_squeeze reduces by doing partial reduction module p
|
|
// where t is result of previous h*r from mul_h_by_r calls.
|
|
fn poly1305_squeeze(mut h Uint192, t [4]u64) {
|
|
// we need to reduce 4 of 64 bit limbs in t modulo 2¹³⁰ - 5.
|
|
// we follow the go version, by splitting result at the 2¹³⁰ mark into h and cc, the carry.
|
|
mut ac := Uint192{
|
|
lo: t[0]
|
|
mi: t[1]
|
|
hi: t[2] & poly1305.mask_low2bits
|
|
}
|
|
mut cc := unsigned.uint128_new(t[2] & poly1305.mask_high62bits, t[3])
|
|
// reduction of general mersene prime, x = c * 2¹³⁰ + h = c * 5 + h (mod 2¹³⁰ - 5)
|
|
// because 2¹³⁰ = 5 (mod 2¹³⁰ - 5)
|
|
// here, we follow the go version
|
|
|
|
mut c := u64(0)
|
|
ac, c = ac.add_128_checked(cc, c)
|
|
cc = shift_right_by2(mut cc)
|
|
|
|
// once again
|
|
ac, c = ac.add_128_checked(cc, 0)
|
|
// updates accumulator
|
|
h = ac
|
|
}
|