package address_test import ( "crypto/ed25519" "encoding/hex" "testing" "git.n1ko.dev/Niko/niko_trust/pkg/address" ) // smallOrderKeys is the standard list of Edwards25519 points of order 1, 2, 4 // and 8, plus the non-canonical encodings of some of them. These are the keys // for which Ed25519 signature verification is degenerate. // // The list is the one used by libsodium's crypto_core_ed25519_is_valid_point // test vectors and by RFC 8032 implementation reports. var smallOrderKeys = []struct { name string hex string }{ {"order 1 (identity)", "0100000000000000000000000000000000000000000000000000000000000000"}, {"order 2", "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"}, {"order 4 (all zero)", "0000000000000000000000000000000000000000000000000000000000000000"}, {"order 4 (sign bit)", "0000000000000000000000000000000000000000000000000000000000000080"}, {"order 8 a", "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05"}, {"order 8 b", "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa"}, {"order 8 c", "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85"}, {"order 8 d", "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a"}, {"non-canonical p", "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"}, {"non-canonical p+1", "eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"}, } // TestRejectsSmallOrderKeys is the most important test in this package. // // crypto/ed25519.Verify accepts these keys and, for several of them, verifies // an all-zero signature against ANY message. If such a key could be turned // into a trust address, its owner would hold an identity whose signature // verifies on every claim and every approval anyone constructs. That is a // direct break of INV-1 and of the whole "cryptography establishes who said // something" premise. func TestRejectsSmallOrderKeys(t *testing.T) { for _, tc := range smallOrderKeys { t.Run(tc.name, func(t *testing.T) { key, err := hex.DecodeString(tc.hex) if err != nil { t.Fatal(err) } if err := address.ValidatePubKey(key); err == nil { t.Fatalf("ValidatePubKey accepted a degenerate key") } if _, err := address.FromPubKey(key); err == nil { t.Fatalf("FromPubKey produced an address for a degenerate key") } }) } } // TestDegenerateKeyWouldForgeSignatures demonstrates the concrete attack the // validation prevents, so that a future reader cannot mistake the check for // paranoia and remove it. func TestDegenerateKeyWouldForgeSignatures(t *testing.T) { // The order-1 point (the identity element, y = 1). Signing with R set to // the same encoding and S = 0 yields a signature that verifies under // crypto/ed25519 for every message without exception. key, err := hex.DecodeString("0100000000000000000000000000000000000000000000000000000000000000") if err != nil { t.Fatal(err) } universalSig := make([]byte, ed25519.SignatureSize) copy(universalSig[:32], key) messages := []string{ "trust1abc says trust1bca minecraft.op = true", "trust1abc asks trust1bca to ban Steve", "an entirely unrelated statement", "", } forgedAll := true for _, m := range messages { if !ed25519.Verify(key, []byte(m), universalSig) { forgedAll = false break } } if forgedAll { t.Log("crypto/ed25519.Verify accepts one fixed signature under this key " + "for every message tested: a universal forgery") } else { t.Log("stdlib no longer exhibits the universal forgery; validation is still required") } // Whatever the stdlib does, this key must never become a trust identity. if err := address.ValidatePubKey(key); err == nil { t.Fatal("degenerate key must be rejected before it can become an identity") } if _, err := address.FromPubKey(key); err == nil { t.Fatal("FromPubKey must refuse the degenerate key") } // And it must not be smuggled in through the textual form either. if _, err := address.Parse(encodeRawForTest(t, address.Version0, key)); err == nil { t.Fatal("Parse must refuse an address wrapping the degenerate key") } } func TestRejectsWrongKeySize(t *testing.T) { for _, n := range []int{0, 1, 31, 33, 64} { if err := address.ValidatePubKey(make([]byte, n)); err == nil { t.Errorf("accepted %d-byte key", n) } } if err := address.ValidatePubKey(nil); err == nil { t.Error("accepted nil key") } } // TestAcceptsGeneratedKeys ensures the validation never rejects an honest key. // A false positive here would randomly break real users, so the sample is // large enough to catch a rate on the order of one in a thousand. func TestAcceptsGeneratedKeys(t *testing.T) { const n = 3000 for i := 0; i < n; i++ { pub, _, err := ed25519.GenerateKey(nil) if err != nil { t.Fatal(err) } if err := address.ValidatePubKey(pub); err != nil { t.Fatalf("rejected an honestly generated key: %v (%x)", err, pub) } if _, err := address.FromPubKey(pub); err != nil { t.Fatalf("FromPubKey rejected an honest key: %v", err) } } t.Logf("accepted %d generated keys, rejected 0", n) } // TestParseRejectsEmbeddedSmallOrderKey checks that the validation cannot be // bypassed by encoding a degenerate key into a well-formed bech32m address. func TestParseRejectsEmbeddedSmallOrderKey(t *testing.T) { for _, tc := range smallOrderKeys { key, _ := hex.DecodeString(tc.hex) s := encodeRawForTest(t, address.Version0, key) if _, err := address.Parse(s); err == nil { t.Fatalf("Parse accepted an address wrapping a degenerate key (%s): %s", tc.name, s) } } }