Skip to content

Commit

Permalink
Fix secretKey bigger than curve order and stack smashing in ECP mul (#41
Browse files Browse the repository at this point in the history
)

* Move to openArray API (internal) for HKDF and HashToG2

* Fix #40 - Milagro can't parse integer with more than 381 used bits in a BIG_384 (but doesn't error)
  • Loading branch information
mratsim authored Mar 11, 2020
1 parent 9a14335 commit e6849cd
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 54 deletions.
15 changes: 6 additions & 9 deletions blscurve/bls_signature_scheme.nim
Original file line number Diff line number Diff line change
Expand Up @@ -513,11 +513,7 @@ func keyGen*(ikm: openarray[byte], publicKey: var PublicKey, secretKey: var Secr
var prk: MDigest[sha256.bits]

# 1. PRK = HKDF-Extract("BLS-SIG-KEYGEN-SALT-", IKM)
ctx.hkdfExtract(
prk,
cast[ptr byte](salt[0].unsafeAddr), salt.len.uint,
ikm[0].unsafeAddr, ikm.len.uint
)
ctx.hkdfExtract(prk, salt, ikm)

# curve order r = 52435875175126190479447740508185965837690552500527637822603658699938581184513
# const L = ceil((1.5 * ceil(log2(r))) / 8) = 48
Expand All @@ -526,17 +522,18 @@ func keyGen*(ikm: openarray[byte], publicKey: var PublicKey, secretKey: var Secr
# 2. OKM = HKDF-Expand(PRK, "", L)
const L = 48
var okm: array[L, byte]
ctx.hkdfExpand(prk, nil, 0, okm[0].addr, L)
ctx.hkdfExpand(prk, "", okm)

# 3. x = OS2IP(OKM) mod r
# 5. SK = x
if not secretKey.intVal.fromBytes(okm):
var dseckey: DBIG_384
if not dseckey.fromBytes(okm):
return false

{.noSideEffect.}:
BIG_384_mod(secretKey.intVal, CURVE_Order)
BIG_384_dmod(secretKey.intVal, dseckey, CURVE_Order)

# 4. xP = x * P
# 6. PK = point_to_pubkey(xP)
publicKey = privToPub(secretKey)

return true
35 changes: 16 additions & 19 deletions blscurve/hash_to_curve.nim
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ import

func hashToBaseFP2[T](
ctx: var HMAC[T],
msg: ptr byte, msgLen: uint,
msg: ptr byte, msgLen: int,
ctr: range[0'i8 .. 2'i8],
domainSepTag: ptr byte,
domainSepTagLen: uint
domainSepTag: string,
): FP2_BLS381 =
## Implementation of hash_to_base for the G2 curve of BLS12-381
## https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-04#section-5.3
Expand Down Expand Up @@ -84,7 +83,10 @@ func hashToBaseFP2[T](
# with an extra null-byte beyond the declared length.
assert not msg.isNil
assert cast[ptr UncheckedArray[byte]](msg)[msgLen] == 0x00, "Expected message terminated by nul-byte but found " & $cast[ptr UncheckedArray[byte]](msg)[msgLen] & " (decimal value)"
hkdfExtract(ctx, mprime, domainSepTag, domainSepTagLen, msg, msgLen+1)
hkdfExtract(
ctx, mprime, domainSepTag,
toOpenArray(cast[ptr UncheckedArray[byte]](msg), 0, msgLen) # msgLen inclusive
)

info[0] = ord'H'
info[1] = ord'2'
Expand All @@ -96,7 +98,7 @@ func hashToBaseFP2[T](
## for i in 1 .. m
## with m = 2 (extension degree of FP2)
info[4] = byte(i)
hkdfExpand(ctx, mprime, info[0].addr, info.len.uint, t[0].addr, t.len.uint)
hkdfExpand(ctx, mprime, info, t)

block: # HKDF output is greater than 384-bit (64 bytes = 512-bit)
# and need to be stored and reduced in a DBIG
Expand Down Expand Up @@ -404,13 +406,13 @@ func clearCofactor(P: var ECP2_BLS381) =

P.affine() # Convert from Jacobian coordinates (x', y', z') to affine (x, y, 1); (x is not the curve parameter here)

func hashToG2(msg: ptr byte, msgLen: uint, domainSepTag: ptr byte, domainSepTagLen: uint): ECP2_BLS381 =
func hashToG2(msg: ptr byte, msgLen: int, domainSepTag: string): ECP2_BLS381 =
## Hash an arbitrary message to the G2 curve of BLS12-381
## The message should have an extra null byte after its declared length
var ctx: HMAC[sha256]

let u0 = hashToBaseFP2(ctx, msg, msgLen, ctr = 0, domainSepTag, domainSepTagLen)
let u1 = hashToBaseFP2(ctx, msg, msgLen, ctr = 1, domainSepTag, domainSepTagLen)
let u0 = hashToBaseFP2(ctx, msg, msgLen, ctr = 0, domainSepTag)
let u1 = hashToBaseFP2(ctx, msg, msgLen, ctr = 1, domainSepTag)

result = mapToCurveG2(u0)
let Q1 = mapToCurveG2(u1)
Expand All @@ -427,31 +429,27 @@ func hashToG2*(msg: openarray[byte], domainSepTag: string): ECP2_BLS381 =
msgWithNul[0 ..< msg.len] = msg
msgWithNul[msg.len] = 0x00

let
pmsg = cast[ptr byte](msgWithNul[0].unsafeAddr)
pdst = cast[ptr byte](domainSepTag[0].unsafeAddr)
let pmsg = cast[ptr byte](msgWithNul[0].unsafeAddr)

hashToG2(pmsg, msg.len.uint, pdst, domainSepTag.len.uint)
hashToG2(pmsg, msg.len, domainSepTag)

func hashToG2*(message, domainSepTag: string): ECP2_BLS381 =
## Hash an arbitrary message to the G2 curve of BLS12-381
## The message should have an extra null byte

let pdst = cast[ptr byte](domainSepTag[0].unsafeAddr)

if message.len == 0:
# Special-casing empty strings (i.e. not constant-time)
# is not an issue because empty strings are always vulnerable
# to timing attacks.
var empty = [byte 0x00]
let pmsg = cast[ptr byte](empty[0].unsafeAddr)
result = hashToG2(pmsg, 0, pdst, domainSepTag.len.uint)
result = hashToG2(pmsg, 0, domainSepTag)
else:
# Strings are always nul-terminated in Nim so don't need
# extra nul appending compared to openarray[byte]
# to satisfy the spec requirements
let pmsg = cast[ptr byte](message[0].unsafeAddr)
result = hashToG2(pmsg, message.len.uint, pdst, domainSepTag.len.uint)
result = hashToG2(pmsg, message.len, domainSepTag)

# Unofficial test vectors for hashToG2 primitives
# ----------------------------------------------------------------------
Expand Down Expand Up @@ -490,15 +488,14 @@ when isMainModule:

let pmsg = if msg.len == 0: nil
else: cast[ptr byte](msg[0].unsafeAddr)
let pdst = cast[ptr byte](dst[0].unsafeAddr)

var ctx: HMAC[sha256]
# Important: do we need to include the null byte at the end?
let pointFP2 = hashToBaseFP2(
ctx,
pmsg, msg.len.uint,
pmsg, msg.len,
ctr,
pdst, dst.len.uint
dst
)
doAssert fp2 == pointFP2
echo "Success hashToBaseFP2 ", astToStr(id)
Expand Down
55 changes: 29 additions & 26 deletions blscurve/hkdf.nim
Original file line number Diff line number Diff line change
Expand Up @@ -80,49 +80,48 @@

import nimcrypto/hmac

func hkdfExtract*[T](ctx: var HMAC[T],
func hkdfExtract*[T;S,I: char|byte](ctx: var HMAC[T],
prk: var MDigest[T.bits],
salt: ptr byte, saltLen: uint,
ikm: ptr byte, ikmLen: uint
salt: openArray[S],
ikm: openArray[I]
) =
## "Extract" step of HKDF.
## Extract a fixed size pseudom-random key
## from an optional salt value
## and a secret input keying material.
##
## Inputs:
## - salt + saltLen: a buffer to an optional salt value (set to nil if unused)
## - ikm + ikmLen: "input keying material", the secret value to hash.
## - salt: a buffer to an optional salt value (set to nil if unused)
## - ikm: "input keying material", the secret value to hash.
##
## Output:
## - prk: a pseudo random key of fixed size. The size is the same as the cryptographic hash chosen.
##
## Temporary:
## - ctx: a HMAC["cryptographic-hash"] context, for example HMAC[sha256].
mixin init, update, finish
ctx.init(salt, saltLen)
ctx.update(ikm, ikmLen)
ctx.init(salt)
ctx.update(ikm)
discard ctx.finish(prk.data)

# ctx.clear() - TODO: very expensive

func hkdfExpand*[T](ctx: var HMAC[T],
func hkdfExpand*[T;I: char|byte](ctx: var HMAC[T],
prk: MDigest[T.bits],
info: ptr byte, infoLen: uint,
output: ptr byte, outLen: uint
info: openArray[I],
output: var openArray[byte]
) =
## "Expand" step of HKDF
## Expand a fixed size pseudo random-key
## into several pseudo-random keys
##
## Inputs:
## - prk: a pseudo random key (PRK) of fixed size. The size is the same as the cryptographic hash chosen.
## - info + infolen: optional context and application specific information (set to nil if unused)
## - outLen: The target length of the output
## - info: optional context and application specific information (set to nil if unused)
##
## Output:
## - output: OKM (output keying material). The PRK is expanded to match
## outLen, the result is tored in output.
## the output length, the result is tored in output.
##
## Temporary:
## - ctx: a HMAC["cryptographic-hash"] context, for example HMAC[sha256].
Expand All @@ -131,23 +130,23 @@ func hkdfExpand*[T](ctx: var HMAC[T],
const HashLen = T.bits div 8

static: doAssert T.bits >= 0
# assert outLen <= 255*HashLen
# assert output.len <= 255*HashLen

let N = outLen div HashLen
let N = output.len div HashLen
var t: MDigest[T.bits]
let oArray = cast[ptr UncheckedArray[byte]](output)

for i in 0'u .. N:
for i in 0 .. N:
ctx.init(prk.data)
# T(0) = empty string
if i != 0:
ctx.update(t.data)
ctx.update(info, infoLen)
ctx.update(info)
ctx.update([uint8(i+1)])
discard ctx.finish(t.data)

let iStart = i * HashLen
let size = min(HashLen, int(outLen - iStart))
let size = min(HashLen, output.len - iStart)
copyMem(oArray[iStart].addr, t.data.addr, size)

# ctx.clear() - TODO: very expensive
Expand Down Expand Up @@ -178,15 +177,19 @@ when isMainModule:
var ctx: HMAC[HashType]
var prk: MDigest[HashType.bits]

let salt = if bsalt.len == 0: nil
else: bsalt[0].unsafeAddr
let ikm = if bikm.len == 0: nil
else: bikm[0].unsafeAddr
let info = if binfo.len == 0: nil
else: binfo[0].unsafeAddr
# let salt = if bsalt.len == 0: nil
# else: bsalt[0].unsafeAddr
# let ikm = if bikm.len == 0: nil
# else: bikm[0].unsafeAddr
# let info = if binfo.len == 0: nil
# else: binfo[0].unsafeAddr
let
salt = bsalt
ikm = bikm
info = binfo

hkdfExtract(ctx, prk, salt, bsalt.len.uint, ikm, bikm.len.uint)
hkdfExpand(ctx, prk, info, binfo.len.uint, output[0].addr, output.len.uint)
hkdfExtract(ctx, prk, salt, ikm)
hkdfExpand(ctx, prk, info, output)

doAssert @(prk.data) == bprk, "\nComputed 0x" & toHex(prk.data) &
"\nbut expected " & PRK & '\n'
Expand Down

0 comments on commit e6849cd

Please sign in to comment.