commit a879a6e6c101872e5185d9d99a8e8df932effaa6
parent 2f9ffa5077552ed03dfde8b966634cee96386dd7
Author: n0tr1v <n0tr1v@protonmail.com>
Date: Sun, 29 Jan 2023 05:59:26 -0800
script to sign an verify tor ed25519
Diffstat:
1 file changed, 353 insertions(+), 0 deletions(-)
diff --git a/cmd/onion_signer/main.go b/cmd/onion_signer/main.go
@@ -0,0 +1,353 @@
+package main
+
+import (
+ "bytes"
+ "crypto/ed25519"
+ "crypto/sha512"
+ "encoding/base32"
+ "encoding/pem"
+ "fmt"
+ "golang.org/x/crypto/sha3"
+ "math/big"
+ "os"
+ "strings"
+)
+
+func main() {
+ onionAddress := "your_onion_addr.onion"
+ pkPath := "/path/to/hs_ed25519_secret_key"
+ msg := []byte("message by n0tr1v")
+
+ // Generate signature
+ pemKeyBytes, _ := os.ReadFile(pkPath)
+ identityPrivKey := LoadTorKeyFromDisk(pemKeyBytes)
+ identityPubKey := PublickeyFromESK(identityPrivKey)
+ fmt.Println("Onion address: ", AddressFromIdentityKey(identityPubKey))
+ signature := sign(identityPrivKey, msg)
+ pemSignature := string(pem.EncodeToMemory(&pem.Block{Type: "SIGNATURE", Bytes: signature}))
+ fmt.Println(pemSignature)
+
+ // Verify signature
+ block, _ := pem.Decode([]byte(pemSignature))
+ if block == nil {
+ panic("failed to extract signature")
+ }
+ sig := block.Bytes
+ pub := identityKeyFromAddress(onionAddress)
+ isValid := ed25519.Verify(pub, msg, sig)
+ if !isValid {
+ panic("invalid signature")
+ }
+ fmt.Println("signature is valid")
+}
+
+var (
+ bB1 = []*big.Int{biMod(bx, q), biMod(by, q), bi(1), biMod(biMul(bx, by), q)}
+ l = biAdd(biExp(bi(2), bi(252)), biFromStr("27742317777372353535851937790883648493"))
+ b = 256
+ by = biMul(bi(4), inv(bi(5)))
+ bx = xrecover(by)
+ q = biSub(biExp(bi(2), bi(255)), bi(19))
+ bB = []*big.Int{biMod(bx, q), biMod(by, q)}
+ I = expmod(bi(2), biDiv(biSub(q, bi(1)), bi(4)), q)
+ d = bi(0).Mul(bi(-121665), inv(bi(121666)))
+ d1 = biMod(biMul(bi(-121665), inv(bi(121666))), q)
+)
+
+func identityKeyFromAddress(onionAddr string) ed25519.PublicKey {
+ trimmedAddr := strings.TrimSuffix(onionAddr, ".onion")
+ upperAddr := strings.ToUpper(trimmedAddr)
+ decodedAddr, _ := base32.StdEncoding.DecodeString(upperAddr)
+ return decodedAddr[:32]
+}
+
+func sign(identityPrivKey, msg []byte) []byte {
+ return signatureWithESK(msg, identityPrivKey, PublickeyFromESK(identityPrivKey))
+}
+
+func biFromStr(v string) (out *big.Int) {
+ out = new(big.Int)
+ _, _ = fmt.Sscan(v, out)
+ return
+}
+
+func signatureWithESK(msg, blindedEsk, blindedKey []byte) []byte {
+ a := decodeInt(blindedEsk[:32])
+ lines := make([][]byte, 0)
+ for i := b / 8; i < b/4; i++ {
+ lines = append(lines, blindedEsk[i:i+1])
+ }
+ toHint := append(bytes.Join(lines, []byte("")), msg...)
+ r := hint(toHint)
+ R := Scalarmult1(bB1, r)
+ S := biMod(biAdd(r, biMul(hint([]byte(string(Encodepoint(R))+string(blindedKey)+string(msg))), a)), l)
+
+ return append(Encodepoint(R), encodeint(S)...)
+}
+
+func edwardsDouble(P []*big.Int) []*big.Int {
+ // This is formula sequence 'dbl-2008-hwcd' from
+ // http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
+ x1 := P[0]
+ y1 := P[1]
+ z1 := P[2]
+ a := biMod(biMul(x1, x1), q)
+ b := biMod(biMul(y1, y1), q)
+ c := biMod(biMul(biMul(bi(2), z1), z1), q)
+ e := biMod(biSub(biSub(biMul(biAdd(x1, y1), biAdd(x1, y1)), a), b), q)
+ g := biAdd(biMul(a, bi(-1)), b)
+ f := biSub(g, c)
+ h := biSub(biMul(a, bi(-1)), b)
+ x3 := biMul(e, f)
+ y3 := biMul(g, h)
+ t3 := biMul(e, h)
+ z3 := biMul(f, g)
+ return []*big.Int{biMod(x3, q), biMod(y3, q), biMod(z3, q), biMod(t3, q)}
+}
+
+func Scalarmult1(P []*big.Int, e *big.Int) []*big.Int {
+ if e.Cmp(bi(0)) == 0 {
+ return []*big.Int{bi(0), bi(1), bi(1), bi(0)}
+ }
+ Q := Scalarmult1(P, biDiv(e, bi(2)))
+ Q = edwardsDouble(Q)
+ if biAnd(e, bi(1)).Int64() == 1 {
+ //if e.And(e, bi(1)).Int64() == 1 {
+ Q = edwardsAdd(Q, P)
+ }
+ return Q
+}
+
+func edwardsAdd(P, Q []*big.Int) []*big.Int {
+ // This is formula sequence 'addition-add-2008-hwcd-3' from
+ // http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
+ x1 := P[0]
+ y1 := P[1]
+ z1 := P[2]
+ t1 := P[3]
+ x2 := Q[0]
+ y2 := Q[1]
+ z2 := Q[2]
+ t2 := Q[3]
+ a := biMod(biMul(biSub(y1, x1), biSub(y2, x2)), q)
+ b := biMod(biMul(biAdd(y1, x1), biAdd(y2, x2)), q)
+ c := biMod(biMul(biMul(biMul(t1, bi(2)), d1), t2), q)
+ dd := biMod(biMul(biMul(z1, bi(2)), z2), q)
+ e := biSub(b, a)
+ f := biSub(dd, c)
+ g := biAdd(dd, c)
+ h := biAdd(b, a)
+ x3 := biMul(e, f)
+ y3 := biMul(g, h)
+ t3 := biMul(e, h)
+ z3 := biMul(f, g)
+ return []*big.Int{biMod(x3, q), biMod(y3, q), biMod(z3, q), biMod(t3, q)}
+}
+
+func Encodepoint(P []*big.Int) []byte {
+ x := P[0]
+ y := P[1]
+ z := P[2]
+ //t := P[3]
+ zi := inv(z)
+ x = biMod(biMul(x, zi), q)
+ y = biMod(biMul(y, zi), q)
+ bits := make([]uint8, 0)
+ for i := 0; i < b-1; i++ {
+ bits = append(bits, uint8(biAnd(biRsh(y, uint(i)), bi(1)).Int64()))
+ }
+ bits = append(bits, uint8(biAnd(x, bi(1)).Int64()))
+ by := make([]uint8, 0)
+ for i := 0; i < b/8; i++ {
+ sum := uint8(0)
+ for j := 0; j < 8; j++ {
+ sum += bits[i*8+j] << j
+ }
+ by = append(by, sum)
+ }
+ return by
+}
+
+func hint(m []byte) *big.Int {
+ tmp := sha512.Sum512(m)
+ h := tmp[:]
+ sum := bi(0)
+ for i := 0; i < 2*b; i++ {
+ sum = biAdd(sum, biMul(biExp(bi(2), bi(int64(i))), bi(int64(Bit(h, int64(i))))))
+ }
+ return sum
+}
+
+func AddressFromIdentityKey(pub ed25519.PublicKey) string {
+ var checksumBytes bytes.Buffer
+ checksumBytes.Write([]byte(".onion checksum"))
+ checksumBytes.Write(pub)
+ checksumBytes.Write([]byte{0x03})
+ checksum := sha3.Sum256(checksumBytes.Bytes())
+ var onionAddressBytes bytes.Buffer
+ onionAddressBytes.Write(pub)
+ onionAddressBytes.Write(checksum[:2])
+ onionAddressBytes.Write([]byte{0x03})
+ addr := strings.ToLower(base32.StdEncoding.EncodeToString(onionAddressBytes.Bytes()))
+ return addr + ".onion"
+}
+
+func encodeint(y *big.Int) []byte {
+ bits := make([]*big.Int, 0)
+ for i := 0; i < b; i++ {
+ bits = append(bits, biAnd(biRsh(y, uint(i)), bi(1)))
+ }
+ final := make([]byte, 0)
+ for i := 0; i < b/8; i++ {
+ sum := bi(0)
+ for j := 0; j < 8; j++ {
+ sum = biAdd(sum, biLsh(bits[i*8+j], uint(j)))
+ }
+ final = append(final, byte(sum.Uint64()))
+ }
+ return final
+}
+
+func PublickeyFromESK(h []byte) ed25519.PublicKey {
+ a := decodeInt(h[:32])
+ A := scalarmult(bB, a)
+ return encodepoint(A)
+}
+
+// LoadTorKeyFromDisk load a private identity key from little-t-tor.
+func LoadTorKeyFromDisk(keyBytes []byte) ed25519.PrivateKey {
+ if !bytes.Equal(keyBytes[:29], []byte("== ed25519v1-secret: type0 ==")) {
+ panic("Tor key does not start with Tor header")
+ }
+ expandedSk := keyBytes[32:]
+
+ // The rest should be 64 bytes (a,h):
+ // 32 bytes for secret scalar 'a'
+ // 32 bytes for PRF key 'h'
+ if len(expandedSk) != 64 {
+ panic("Tor private key has the wrong length")
+ }
+ return expandedSk
+}
+
+func encodepoint(P []*big.Int) []byte {
+ x := P[0]
+ y := P[1]
+ bits := make([]uint8, 0)
+ for i := 0; i < b-1; i++ {
+ bits = append(bits, uint8(biAnd(biRsh(y, uint(i)), bi(1)).Int64()))
+ }
+ bits = append(bits, uint8(biAnd(x, bi(1)).Int64()))
+ by := make([]uint8, 0)
+ for i := 0; i < b/8; i++ {
+ sum := uint8(0)
+ for j := 0; j < 8; j++ {
+ sum += bits[i*8+j] << j
+ }
+ by = append(by, sum)
+ }
+ return by
+}
+
+func decodeInt(s []uint8) *big.Int {
+ sum := bi(0)
+ for i := 0; i < 256; i++ {
+ e := biExp(bi(2), bi(int64(i)))
+ m := bi(int64(Bit(s, int64(i))))
+ sum = sum.Add(sum, biMul(e, m))
+ }
+ return sum
+}
+
+func scalarmult(P []*big.Int, e *big.Int) []*big.Int {
+ if e.Cmp(bi(0)) == 0 {
+ return []*big.Int{bi(0), bi(1)}
+ }
+ Q := scalarmult(P, biDiv(e, bi(2)))
+ Q = edwards(Q, Q)
+ if e.And(e, bi(1)).Int64() == 1 {
+ Q = edwards(Q, P)
+ }
+ return Q
+}
+
+func edwards(P, Q []*big.Int) []*big.Int {
+ x1 := P[0]
+ y1 := P[1]
+ x2 := Q[0]
+ y2 := Q[1]
+ x3 := biMul(biAdd(biMul(x1, y2), biMul(x2, y1)), inv(biAdd(bi(1), biMul(biMul(biMul(biMul(d, x1), x2), y1), y2))))
+ y3 := biMul(biAdd(biMul(y1, y2), biMul(x1, x2)), inv(biSub(bi(1), biMul(biMul(biMul(biMul(d, x1), x2), y1), y2))))
+ return []*big.Int{biMod(x3, q), biMod(y3, q)}
+}
+
+func xrecover(y *big.Int) *big.Int {
+ xx := biMul(biSub(biMul(y, y), bi(1)), inv(biAdd(biMul(biMul(d, y), y), bi(1))))
+ x := expmod(xx, biDiv(biAdd(q, bi(3)), bi(8)), q)
+ if biMod(biSub(biMul(x, x), xx), q).Int64() != 0 {
+ x = biMod(biMul(x, I), q)
+ }
+ if biMod(x, bi(2)).Int64() != 0 {
+ x = biSub(q, x)
+ }
+ return x
+}
+
+func inv(x *big.Int) *big.Int {
+ return expmod(x, biSub(q, bi(2)), q)
+}
+
+func expmod(b, e, m *big.Int) *big.Int {
+ if e.Cmp(bi(0)) == 0 {
+ return bi(1)
+ }
+ t := biMod(biExp(expmod(b, biDiv(e, bi(2)), m), bi(2)), m)
+ if biAnd(e, bi(1)).Int64() == 1 {
+ t = biMod(biMul(t, b), m)
+ }
+ return t
+}
+
+func Bit(h []uint8, i int64) uint8 {
+ return (h[i/8] >> (i % 8)) & 1
+}
+
+func bi(v int64) *big.Int {
+ return big.NewInt(v)
+}
+
+func biExp(a, b *big.Int) *big.Int {
+ return bi(0).Exp(a, b, nil)
+}
+
+func biDiv(a, b *big.Int) *big.Int {
+ return bi(0).Div(a, b)
+}
+
+func biSub(a, b *big.Int) *big.Int {
+ return bi(0).Sub(a, b)
+}
+
+func biAdd(a, b *big.Int) *big.Int {
+ return bi(0).Add(a, b)
+}
+
+func biAnd(a, b *big.Int) *big.Int {
+ return bi(0).And(a, b)
+}
+
+func biLsh(a *big.Int, b uint) *big.Int {
+ return bi(0).Lsh(a, b)
+}
+
+func biRsh(a *big.Int, b uint) *big.Int {
+ return bi(0).Rsh(a, b)
+}
+
+func biMul(a, b *big.Int) *big.Int {
+ return bi(0).Mul(a, b)
+}
+
+func biMod(a, b *big.Int) *big.Int {
+ return bi(0).Mod(a, b)
+}