Linkable Ring Signature

Linkable Spontaneous Anonymous Group Signature for Ad Hoc Groups.

public_keys.go

package client

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/elliptic"
	"encoding/asn1"
	"encoding/hex"
	"encoding/pem"
	"hash"
	"math/big"
	"regexp"
	"sort"
	"strconv"

	"github.com/zbohm/lirisi/ring"
	"github.com/zbohm/lirisi/x509ec"
)

IdentKey holds filename, digest (hash) of file, instance of public key.

type IdentKey struct {
	digest string
	key    *ecdsa.PublicKey
}

HashIdentKey contains the salted hash of public key file and IdentKey.

type HashIdentKey struct {
	hash string
	pub  IdentKey
}

Enter asci character LF.

var Enter = []byte("\n")

FoldPublicKeys create sequence of public keys coordinates.

func FoldPublicKeys(pubKeysContent [][]byte, hashName, format, order string) (int, []byte) {

	var content, keysDigest []byte
	var curve elliptic.Curve
	var publicKeys []HashIdentKey
	var status int

	hashFnc, ok := ring.HashCodes[hashName]
	if !ok {
		return ring.UnexpectedHashType, content
	}
	hasherOID, status := ring.GetHasherOID(hashFnc)
	if status != ring.Success {
		return status, content
	}
	status, publicKeys, keysDigest = decodePublicKeys(pubKeysContent, hashFnc)
	if status != ring.Success {
		return status, content
	}
	if order == "hashes" {
		status, publicKeys, keysDigest = sortKeysByHashes(publicKeys, hashFnc)
		if status != ring.Success {
			return status, content
		}
	}
	pointSeq := ring.FoldedPublicKeys{Name: ring.Origin + " Public keys", HasherOID: hasherOID, Digest: keysDigest}
	compress := true

	for i, item := range publicKeys {
		if i == 0 {
			curve = item.pub.key.Curve
			curveOID, status := ring.GetCurveOIDForCurve(item.pub.key.Curve)
			if status != ring.Success {
				return status, content
			}
			pointSeq.CurveOID = curveOID

Uncompress does not work for these curves: - secp256k1 - brainpoolP256r1 - brainpoolP384r1 - brainpoolP512r1

			oid := curveOID.String()
			if oid == "1.3.132.0.10" || oid == "1.3.36.3.3.2.8.1.1.7" || oid == "1.3.36.3.3.2.8.1.1.11" || oid == "1.3.36.3.3.2.8.1.1.13" {
				compress = false
			}
		} else {
			if item.pub.key.Curve != curve {
				return ring.UnexpectedCurveType, content
			}
		}
		if compress {
			pointSeq.Keys = append(pointSeq.Keys, elliptic.MarshalCompressed(curve, item.pub.key.X, item.pub.key.Y))
		} else {
			pointSeq.Keys = append(pointSeq.Keys, elliptic.Marshal(curve, item.pub.key.X, item.pub.key.Y))
		}
	}
	return encodeFoldedPublicKeys(curve, pointSeq, publicKeys, keysDigest, hashName, format)
}

func encodeFoldedPublicKeys(
	curve elliptic.Curve,
	pointSeq ring.FoldedPublicKeys,
	publicKeys []HashIdentKey,
	keysDigest []byte,
	hashName,
	format string,
) (int, []byte) {
	content, err := asn1.Marshal(pointSeq)
	if err != nil {
		return ring.Asn1MarshalFailed, content
	}
	if format == "PEM" {
		block := &pem.Block{
			Type: "FOLDED PUBLIC KEYS",
			Headers: map[string]string{
				"Origin":       ring.Origin,
				"CurveName":    ring.GetCurveName(curve),
				"CurveOID":     pointSeq.CurveOID.String(),
				"HasherOID":    pointSeq.HasherOID.String(),
				"HasherName":   hashName,
				"NumberOfKeys": strconv.Itoa(len(publicKeys)),
				"Digest":       FormatDigest(hex.EncodeToString(keysDigest)),
			},
			Bytes: content,
		}
		var buff bytes.Buffer
		if err := pem.Encode(&buff, block); err != nil {
			return ring.EncodePEMFailed, content
		}
		content = buff.Bytes()
	}
	return ring.Success, content
}

UnfoldPublicKeysContent restore public keys from sequence.

func UnfoldPublicKeysContent(content []byte) (int, []*ecdsa.PublicKey, ring.FoldedPublicKeys) {

	foldedKeys := ring.FoldedPublicKeys{}

	if matched, _ := regexp.Match(`-+BEGIN FOLDED PUBLIC KEYS`, content); matched {
		block, _ := pem.Decode(content)
		if block == nil {
			return ring.DecodePEMFailure, nil, foldedKeys
		}
		content = block.Bytes
	}

	rest, err := asn1.Unmarshal(content, &foldedKeys)
	if err != nil {
		return ring.Asn1UnmarshalFailed, nil, foldedKeys
	}
	if len(rest) > 0 {

x509: trailing data after ASN.1 of public-key

		return ring.UnexpectedRestOfSignature, nil, foldedKeys
	}
	curveType, success := ring.GetCurve(foldedKeys.CurveOID)
	if !success {
		return ring.OIDCurveNotFound, nil, foldedKeys
	}
	curve := curveType()
	publicKeys := make([]*ecdsa.PublicKey, len(foldedKeys.Keys))

	var x, y *big.Int

	for i, buff := range foldedKeys.Keys {
		if buff[0] == 4 {
			x, y = elliptic.Unmarshal(curve, buff)
		} else {
			x, y = elliptic.UnmarshalCompressed(curve, buff)
		}
		if x == nil || y == nil {
			return ring.NilPointCoordinates, nil, foldedKeys
		}
		if !curve.IsOnCurve(x, y) {
			return ring.InvalidPointCoordinates, nil, foldedKeys
		}
		publicKeys[i] = &ecdsa.PublicKey{Curve: curve, X: x, Y: y}
	}
	return ring.Success, publicKeys, foldedKeys
}

UnfoldPublicKeysIntoBytes restore public keys from sequence.

func UnfoldPublicKeysIntoBytes(foldedPublicKeys []byte, outFormat string) (int, [][]byte) {
	var unfoldedPublicKeys [][]byte

	status, publicKeys, _ := UnfoldPublicKeysContent(foldedPublicKeys)
	if status != ring.Success {
		return status, unfoldedPublicKeys
	}
	for _, pub := range publicKeys {
		content, err := x509ec.MarshalPKIXPublicKey(pub)
		if err != nil {
			return ring.MarshalPKIXPublicKeyFailed, unfoldedPublicKeys
		}
		if outFormat == "PEM" {
			block := &pem.Block{Type: "PUBLIC KEY", Bytes: content}
			buff := bytes.NewBuffer(make([]byte, 0))
			if err := pem.Encode(buff, block); err != nil {
				return ring.EncodePEMFailed, unfoldedPublicKeys
			}
			unfoldedPublicKeys = append(unfoldedPublicKeys, buff.Bytes())
		} else {
			unfoldedPublicKeys = append(unfoldedPublicKeys, content)
		}
	}
	return ring.Success, unfoldedPublicKeys
}

func getXYCoordinates(key *ecdsa.PublicKey) []byte {
	buff := []byte{0x04} // Uncompressed form.
	buff = append(buff, key.X.Bytes()...)
	buff = append(buff, key.Y.Bytes()...)
	return buff
}

PublicKeyXYCoordinates outputs public key coordinates X, Y.

func PublicKeyXYCoordinates(pubicKey []byte) (int, []byte) {
	if matched, _ := regexp.Match(`-+BEGIN PUBLIC KEY`, pubicKey); matched {
		block, _ := pem.Decode(pubicKey)
		if block == nil {
			return ring.DecodePEMFailure, []byte{}
		}
		pubicKey = block.Bytes
	}
	pub, err := x509ec.ParsePKIXPublicKey(pubicKey)
	if err != nil {
		return ring.ParsePKIXPublicKeyFailed, []byte{}
	}
	return ring.Success, getXYCoordinates(pub.(*ecdsa.PublicKey))
}

func decodePublicKeys(pubKeysContent [][]byte, hasher func() hash.Hash) (int, []HashIdentKey, []byte) {
	identKeys := []HashIdentKey{}
	fc := ring.FactoryContext{Hasher: hasher}
	digests := make([]byte, 0)
	var hash string
	var block *pem.Block

	is_pem := regexp.MustCompile(`-+BEGIN PUBLIC KEY`)

	for _, content := range pubKeysContent {
		if is_pem.Match(content) {
			block, _ = pem.Decode(content)
			if block == nil {
				return ring.DecodePEMFailure, identKeys, []byte{}
			}
			content = block.Bytes
		}
		pub, err := x509ec.ParsePKIXPublicKey(content)
		if err != nil {
			return ring.ParsePKIXPublicKeyFailed, identKeys, []byte{}
		}
		hash = hex.EncodeToString(fc.MakeDigest(getXYCoordinates(pub.(*ecdsa.PublicKey))))
		digests = append(digests, hash...)
		digests = append(digests, Enter...)

		hidk := HashIdentKey{pub: IdentKey{
			digest: hash,
			key:    pub.(*ecdsa.PublicKey),
		}}
		identKeys = append(identKeys, hidk)
	}
	return ring.Success, identKeys, fc.MakeDigest(digests)
}

func sortKeysByHashes(publicKeys []HashIdentKey, hashFnc func() hash.Hash) (int, []HashIdentKey, []byte) {

	sortedPublicKeys := []HashIdentKey{}

	fc := ring.FactoryContext{Hasher: hashFnc}

Sort keys by their hashes

	sort.Slice(publicKeys, func(i, j int) bool { return publicKeys[i].pub.digest < publicKeys[j].pub.digest })
	digestSum := hex.EncodeToString(fc.MakeDigest(buildDigest(publicKeys)))

Make digests with digestSum as a salt:

	for _, item := range publicKeys {
		item := HashIdentKey{
			hash: hex.EncodeToString(fc.MakeDigest([]byte(digestSum + item.pub.digest))),
			pub:  item.pub,
		}
		sortedPublicKeys = append(sortedPublicKeys, item)
	}

Sort by hashes with fingerprint as a salt

	sort.Slice(sortedPublicKeys, func(i, j int) bool { return sortedPublicKeys[i].hash < sortedPublicKeys[j].hash })
	digests := make([]byte, 0)
	for _, pk := range sortedPublicKeys {
		digests = append(digests, pk.pub.digest...)
		digests = append(digests, Enter...)
	}
	return ring.Success, sortedPublicKeys, fc.MakeDigest(digests)
}

PublicKeysDigest outputs public keys digest.

func PublicKeysDigest(foldedPublicKeys []byte, separator bool) (int, []byte) {
	var digest []byte

	status, publicKeys, foldedKeys := UnfoldPublicKeysContent(foldedPublicKeys)
	if status != ring.Success {
		return status, digest
	}
	hashFnc, ok := ring.GetHasher(foldedKeys.HasherOID)
	if !ok {
		return ring.UnexpectedHashType, digest
	}
	fc := ring.FactoryContext{Hasher: hashFnc}
	digests := make([]byte, 0)

	for _, pub := range publicKeys {
		hash := hex.EncodeToString(fc.MakeDigest(getXYCoordinates(pub)))
		digests = append(digests, hash...)
		digests = append(digests, Enter...)
	}
	content := hex.EncodeToString(fc.MakeDigest(digests))
	if separator {
		content = FormatDigest(content)
	}
	return ring.Success, []byte(content)
}

func buildDigest(hk []HashIdentKey) []byte {
	digests := make([]byte, 0)
	for _, item := range hk {
		digests = append(digests, item.pub.digest...)
		digests = append(digests, Enter...)
	}
	return digests
}