Added Tor addresses, ed25519 light library
This commit is contained in:
parent
e5709f249b
commit
9c7046aabd
107
ed25519/key.go
Normal file
107
ed25519/key.go
Normal file
|
@ -0,0 +1,107 @@
|
|||
package ed25519
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
goEd25519 "crypto/ed25519"
|
||||
"crypto/sha512"
|
||||
"filippo.io/edwards25519"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type PrivateKey []byte
|
||||
type PublicKey goEd25519.PublicKey
|
||||
|
||||
const (
|
||||
// PublicKeySize is the size, in bytes, of public keys as used in this package.
|
||||
PublicKeySize = 32
|
||||
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
|
||||
PrivateKeySize = PrivateKeyFormSize + PublicKeySize
|
||||
PrivateKeyFormSize = 64
|
||||
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
|
||||
SignatureSize = 64
|
||||
)
|
||||
|
||||
// Sign signs the message with privateKey and returns a signature. It will
|
||||
// panic if len(privateKey) is not PrivateKeySize.
|
||||
func Sign(privateKey PrivateKey, message []byte) []byte {
|
||||
// Outline the function body so that the returned signature can be
|
||||
// stack-allocated.
|
||||
signature := make([]byte, SignatureSize)
|
||||
sign(signature, privateKey, message)
|
||||
return signature
|
||||
}
|
||||
|
||||
func sign(signature, privateKey, message []byte) {
|
||||
if l := len(privateKey); l != PrivateKeySize {
|
||||
panic("ed25519: bad private key length: " + strconv.Itoa(l))
|
||||
}
|
||||
privateKey, publicKey := privateKey[:PrivateKeyFormSize], privateKey[PrivateKeyFormSize:]
|
||||
|
||||
s, _ := edwards25519.NewScalar().SetBytesWithClamping(privateKey[:32])
|
||||
prefix := privateKey[32:]
|
||||
|
||||
mh := sha512.New()
|
||||
mh.Write(prefix)
|
||||
mh.Write(message)
|
||||
messageDigest := make([]byte, 0, sha512.Size)
|
||||
messageDigest = mh.Sum(messageDigest)
|
||||
r, _ := edwards25519.NewScalar().SetUniformBytes(messageDigest)
|
||||
|
||||
R := (&edwards25519.Point{}).ScalarBaseMult(r)
|
||||
|
||||
kh := sha512.New()
|
||||
kh.Write(R.Bytes())
|
||||
kh.Write(publicKey)
|
||||
kh.Write(message)
|
||||
hramDigest := make([]byte, 0, sha512.Size)
|
||||
hramDigest = kh.Sum(hramDigest)
|
||||
k, _ := edwards25519.NewScalar().SetUniformBytes(hramDigest)
|
||||
|
||||
S := edwards25519.NewScalar().MultiplyAdd(k, s, r)
|
||||
|
||||
copy(signature[:32], R.Bytes())
|
||||
copy(signature[32:], S.Bytes())
|
||||
}
|
||||
|
||||
// Verify reports whether sig is a valid signature of message by publicKey. It
|
||||
// will panic if len(publicKey) is not PublicKeySize.
|
||||
func Verify(publicKey PublicKey, message, sig []byte) bool {
|
||||
return goEd25519.Verify(goEd25519.PublicKey(publicKey), message, sig)
|
||||
}
|
||||
|
||||
// NewKeyFromSeed calculates a private key from a seed. It will panic if
|
||||
// len(seed) is not SeedSize. This function is provided for interoperability
|
||||
// with RFC 8032. RFC 8032's private keys correspond to seeds in this
|
||||
// package.
|
||||
func NewKeyFromSeed(seed []byte) PrivateKey {
|
||||
return NewKeyFromStandard(goEd25519.NewKeyFromSeed(seed))
|
||||
}
|
||||
|
||||
// GenerateKey generates a public/private key pair using entropy from rand.
|
||||
// If rand is nil, crypto/rand.Reader will be used.
|
||||
func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) {
|
||||
publicKey, privateKey, err := goEd25519.GenerateKey(rand)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return PublicKey(publicKey), NewKeyFromStandard(privateKey), nil
|
||||
}
|
||||
|
||||
// Public returns the PublicKey corresponding to priv.
|
||||
func (priv PrivateKey) Public() crypto.PublicKey {
|
||||
publicKey := make([]byte, PublicKeySize)
|
||||
copy(publicKey, priv[PrivateKeyFormSize:])
|
||||
return PublicKey(publicKey)
|
||||
}
|
||||
|
||||
// Equal reports whether priv and x have the same value.
|
||||
func (priv PrivateKey) Equal(x crypto.PrivateKey) bool {
|
||||
xx, ok := x.(PrivateKey)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return bytes.Equal(priv, xx)
|
||||
}
|
39
ed25519/utils.go
Normal file
39
ed25519/utils.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package ed25519
|
||||
|
||||
import (
|
||||
goEd25519 "crypto/ed25519"
|
||||
"crypto/sha512"
|
||||
"filippo.io/edwards25519"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func NewKeyFromStandard(key goEd25519.PrivateKey) PrivateKey {
|
||||
if l := len(key); l != goEd25519.PrivateKeySize {
|
||||
panic("ed25519: bad private key length: " + strconv.Itoa(l))
|
||||
}
|
||||
seed, publicKey := key[:goEd25519.SeedSize], key[goEd25519.SeedSize:]
|
||||
|
||||
h := sha512.Sum512(seed)
|
||||
s, _ := edwards25519.NewScalar().SetBytesWithClamping(h[:32])
|
||||
|
||||
// (a || RH)
|
||||
priv := s.Bytes()
|
||||
priv = append(priv, publicKey...)
|
||||
return append(priv, h[32:]...)
|
||||
}
|
||||
|
||||
func NewKeyFromRaw(h []byte) PrivateKey {
|
||||
if l := len(h); l != PrivateKeyFormSize {
|
||||
panic("ed25519: bad private key form length: " + strconv.Itoa(l))
|
||||
}
|
||||
|
||||
s, _ := edwards25519.NewScalar().SetBytesWithClamping(h[:32])
|
||||
A := (&edwards25519.Point{}).ScalarBaseMult(s)
|
||||
publicKey := A.Bytes()
|
||||
|
||||
privateKey := make(PrivateKey, PrivateKeySize)
|
||||
copy(privateKey, h)
|
||||
copy(privateKey[PrivateKeyFormSize:], publicKey)
|
||||
|
||||
return privateKey
|
||||
}
|
7
go.mod
7
go.mod
|
@ -1,3 +1,10 @@
|
|||
module git.gammaspectra.live/givna.me/dns-api
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.0.0
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
|
||||
)
|
||||
|
||||
require golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
|
||||
|
|
6
go.sum
Normal file
6
go.sum
Normal file
|
@ -0,0 +1,6 @@
|
|||
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
|
||||
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
53
utils.go
Normal file
53
utils.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package dns_api
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"git.gammaspectra.live/givna.me/dns-api/ed25519"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var base32Encoding = base32.NewEncoding(strings.ToLower("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567")).WithPadding(base32.NoPadding)
|
||||
|
||||
const onionV3Version = byte(0x3)
|
||||
const onionChecksumData = ".onion checksum"
|
||||
|
||||
func PublicKeyToOnionV3(publicKey ed25519.PublicKey) string {
|
||||
checksumBuf := make([]byte, 0, ed25519.PublicKeySize+1+len(onionChecksumData))
|
||||
buf := make([]byte, 0, ed25519.PublicKeySize+2+1)
|
||||
|
||||
checksumBuf = append(checksumBuf, onionChecksumData...)
|
||||
checksumBuf = append(checksumBuf, publicKey...)
|
||||
checksumBuf = append(checksumBuf, onionV3Version)
|
||||
|
||||
h := sha3.New256()
|
||||
h.Write(checksumBuf)
|
||||
checksum := h.Sum([]byte{})[:2]
|
||||
|
||||
buf = append(buf, publicKey...)
|
||||
buf = append(buf, checksum...)
|
||||
buf = append(buf, onionV3Version)
|
||||
return base32Encoding.EncodeToString(buf)
|
||||
}
|
||||
|
||||
const torPrivateKeyPrefix = "== ed25519v1-secret: type0 ==\x00\x00\x00"
|
||||
const torPublicKeyPrefix = "== ed25519v1-public: type0 ==\x00\x00\x00"
|
||||
|
||||
func DecodeTorPrivateKey(key string) ed25519.PrivateKey {
|
||||
priv, err := base64.RawStdEncoding.DecodeString(key)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ed25519.NewKeyFromRaw(priv[32:])
|
||||
}
|
||||
|
||||
func DecodePrivateKey(buf string) ed25519.PrivateKey {
|
||||
priv, err := base64.RawStdEncoding.DecodeString(buf)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ed25519.NewKeyFromRaw(priv)
|
||||
}
|
21
utils_test.go
Normal file
21
utils_test.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package dns_api
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/givna.me/dns-api/ed25519"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const testPrivateKey = "PT0gZWQyNTUxOXYxLXNlY3JldDogdHlwZTAgPT0AAAC4WKtXqyPlfMRwf37uSI/0kZRY8rzFBCpcDPeCp15uUalOhEmCQtBYH7oihSe8J4Znf9Gzw1E7W377Y6K3ux8V"
|
||||
const testOnionAddress = "testdqzsnwe6dpmbhgcm5arrvhqzhomxs27nn2sertphnmfx7z44gzyd"
|
||||
|
||||
func TestPublicKeyToOnionV3(t *testing.T) {
|
||||
privateKey := DecodeTorPrivateKey(testPrivateKey)
|
||||
if privateKey == nil {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
generatedAddress := PublicKeyToOnionV3(privateKey.Public().(ed25519.PublicKey))
|
||||
if generatedAddress != testOnionAddress {
|
||||
t.Errorf("expected address %s, got %s", testOnionAddress, generatedAddress)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue