Hashing Data To A Scalar and to a Point in ECC

One of the great advancements with the Internet has been RFC (Request For Comment) documents and Internet Engineering Task Force (IETF)…

Photo by Mauro Sbicego on Unsplash

Hashing Data To A Scalar and to a Point in ECC

One of the great advancements with the Internet has been RFC (Request For Comment) documents and Internet Engineering Task Force (IETF) Internet-Drafts. The classics of such as RFC791 (IPv6) and RFC793 (TCP) brought a level of conformity that broke the dominance of large companies and countries in defining standards.

A new Internet-Draft [here] brings a new standardization for hashing data values into curves. This can be in the form of hashing a value to a scalar — which might then be used to multiply a point — or onto a curve:

Its importance is due to the increasing usage of elliptic curve methods and their application into zero-knowledge proofs (ZKPs) and multiparty computation (MPC).

For the hashing of our data to a scalar, we use the ExpandMessageXmd function. This produces a uniformly random byte string using a cryptographic hash function H() that outputs b bits. Overall we typically use SHA-256 or SHA-512 for the hashing function. As an input, we use a message string and a DST string. The method defined [here]:

expand_message_xmd(msg, DST, len_in_bytes)
   Parameters:
- H, a hash function (see requirements above).
- b_in_bytes, b / 8 for b the output size of H in bits.
For example, for b = 256, b_in_bytes = 32.
- s_in_bytes, the input block size of H, measured in bytes (see
discussion above). For example, for SHA-256, s_in_bytes = 64.
   Input:
- msg, a byte string.
- DST, a byte string of at most 255 bytes.
See below for information on using longer DSTs.
- len_in_bytes, the length of the requested output in bytes,
not greater than the lesser of (255 * b_in_bytes) or 2^16-1.
   Output:
- uniform_bytes, a byte string.
   Steps:
1. ell = ceil(len_in_bytes / b_in_bytes)
2. ABORT if ell > 255
3. DST_prime = DST || I2OSP(len(DST), 1)
4. Z_pad = I2OSP(0, s_in_bytes)
5. l_i_b_str = I2OSP(len_in_bytes, 2)
6. msg_prime = Z_pad || msg || l_i_b_str || I2OSP(0, 1) || DST_prime
7. b_0 = H(msg_prime)
8. b_1 = H(b_0 || I2OSP(1, 1) || DST_prime)
9. for i in (2, ..., ell):
10. b_i = H(strxor(b_0, b_(i - 1)) || I2OSP(i, 1) || DST_prime)
11. uniform_bytes = b_1 || ... || b_ell
12. return substr(uniform_bytes, 0, len_in_bytes)

Using a DST string of “”P256_XMD:SHA-256_SSWU_RO_”, the Golang call for the Kryptography library, and for 48-byte output is:

xmd, _ := core.ExpandMessageXmd(sha256.New, msg, []byte("P256_XMD:SHA-256_SSWU_RO_"), 48)

After this, we convert xmd into a Big Integer:

v := new(big.Int).SetBytes(xmd)

And then calculate this value modulo of the order of the curve:

res := v.Mod(v, elliptic.P256().Params().N)

We can use the Kryptology library developed by Coinbase to implement the matching [here]:

package main
import (
"crypto/elliptic"
"crypto/sha256"
"fmt"
"math/big"
"os"
"strings"
	"github.com/coinbase/kryptology/pkg/core"
"github.com/coinbase/kryptology/pkg/core/curves"
)
func getCurve(s string) *curves.Curve {
	if strings.Contains(s, "p256") {
return curves.P256()
} else if strings.Contains(s, "k256") {
return curves.K256()
} else if strings.Contains(s, "pallas") {
return curves.PALLAS()
} else if strings.Contains(s, "ed25519") {
return curves.ED25519()
} else if strings.Contains(s, "bls12381g1") {
return curves.BLS12381G1()
} else if strings.Contains(s, "bls12381g2") {
return curves.BLS12381G2()
}
return curves.K256()
}
func main() {
	m := "abc"
ctype := "secp256k1"
	argCount := len(os.Args[1:])
	if argCount > 0 {
m = os.Args[1]
}
if argCount > 1 {
ctype = strings.ToLower(os.Args[2])
}
msg := []byte(m)
	curve := getCurve(ctype)
	toPoint := curve.Point.Hash(msg)
	fmt.Printf("Curve type: [%s]\n", curve.Name)
fmt.Printf("Message: [%s]\n", msg)
	fmt.Printf("\n=== Hash to scalar ===\n")
toScalar := curve.Scalar.Hash(msg)
fmt.Printf("Scalar: %x\n", toScalar.Bytes())
fmt.Printf(" Scalar: %s\n", toScalar.BigInt())
	fmt.Printf("=== Checking answer with Xmd ===\n")
xmd, _ := core.ExpandMessageXmd(sha256.New, msg, []byte("P256_XMD:SHA-256_SSWU_RO_"), 48)
	v := new(big.Int).SetBytes(xmd)
res := v.Mod(v, elliptic.P256().Params().N)
fmt.Printf("Scalar: %x\n", res.Bytes())
fmt.Printf(" Scalar: %s\n", res)
	fmt.Printf("\n=== Hash to point ===\n")
fmt.Printf("Point: %x\n", toPoint.ToAffineUncompressed())
	if strings.Contains(ctype, "256") {
fmt.Printf("X: %x\n", toPoint.ToAffineUncompressed()[1:33])
fmt.Printf("Y: %x\n", toPoint.ToAffineUncompressed()[33:])
} else {
lenb := len(toPoint.ToAffineUncompressed())
fmt.Printf("X: %x\n", toPoint.ToAffineUncompressed()[0:lenb/2])
fmt.Printf("Y: %x\n", toPoint.ToAffineUncompressed()[lenb/2:lenb])
}
}

A sample run [here]:

Curve type: [P-256]
Message: [abc]
=== Hash to scalar ===
Scalar: a9c116c8ec12c43020380c1fb94df0d230031a3eae63c91bee6bb0d99e5c7144
Scalar: 76782030149344631499467647523783879996886697622068133520146022456512145551684
=== Checking answer with Xmd ===
Scalar: a9c116c8ec12c43020380c1fb94df0d230031a3eae63c91bee6bb0d99e5c7144
Scalar: 76782030149344631499467647523783879996886697622068133520146022456512145551684
=== Hash to point ===
Point: 04caf6a25d6ef35978898e2e3e8ba602deac6ca818a1fc1c96976f87a6fe24e002c0ae4e1e7baa98bbd3fc7eaccd42e965920f58acda1f8152426ab501f72625d8
X: caf6a25d6ef35978898e2e3e8ba602deac6ca818a1fc1c96976f87a6fe24e002
Y: c0ae4e1e7baa98bbd3fc7eaccd42e965920f58acda1f8152426ab501f72625d8

Conclusions

So, say thank you to RFCs and Internet-Drafts for the ability for systems to intercommunicate. The new hashing standard for hashing to scalars and curves is interesting, especially in their applications to privacy and resilience.