decred / dcrd

Decred daemon in Go (golang).

Home Page:https://decred.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

stdaddr: PubKeyEd25519V0 from string has different AddressPubKeyHash.

JoeGruffins opened this issue · comments

Code:

package main

import (
	"fmt"

	"github.com/decred/dcrd/chaincfg/v3"
	"github.com/decred/dcrd/txscript/v4/stdaddr"
)

var params = chaincfg.TestNet3Params()

func main() {
	b := make([]byte, 33)
	b[32] = 1
	addrFromRaw, err := stdaddr.NewAddressPubKeyEd25519V0Raw(b, params)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%s %[1]T\n", addrFromRaw)
	fmt.Printf("%s %[1]T\n", addrFromRaw.AddressPubKeyHash())

	addrFromString, err := stdaddr.DecodeAddress(addrFromRaw.String(), params)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%s %[1]T\n", addrFromString)
	apkh := addrFromString.(stdaddr.AddressPubKeyHasher)
	fmt.Printf("%s %[1]T\n", apkh.AddressPubKeyHash())
}

Result:

TkKnVfd6EvzEYAqiELWstkASHgVyYH8JK3gNvAxUX79C9CrnsV8W6 *stdaddr.AddressPubKeyEd25519V0
TedZCnJ5uQ8z7VzKqdBhP1WP2RBYaaoCiUe *stdaddr.AddressPubKeyHashEd25519V0
TkKnVfd6EvzEYAqiELWstkASHgVyYH8JK3gNvAxUX79C9CrnsV8W6 *stdaddr.AddressPubKeyEd25519V0
Tead9n1wLBgaUR7AZrrtt6WWeDfetbF3dpy *stdaddr.AddressPubKeyHashEd25519V0

I would expect AddressPubKeyHash() to produce the same pubkey hash, but for some reason it differs.

Adding

pker := addrFromString.(stdaddr.SerializedPubKeyer)
fmt.Printf("%x %[1]T\n", pker.SerializedPubKey())

Will also show

0000000000000000000000000000000000000000000000000000000000000000 []uint8

So the last byte is dropped.

That's because you're using an invalid public key. Ed25519 public keys are 32 bytes and if you pass in a raw one that is larger, it will be truncated in the first case, but not in the case of hashing the entire thing.

I see. User error then? Should they both truncate or you can close this if it's ok as is.

Well, it is improper use, yes, but I also think it's fair to say the API should probably have an additional length check and reject keys that are the wrong length to catch such cases earlier thereby preventing them altogether.

The secp256k1 case already catches such cases because the secp256k1.ParsePubKey method is extremely strict. The ed25519 variant (edwards.ParsePubKey) is not as strict and relies more on the callers providing correct data, so it just truncates if callers provide keys that are too large which is really the underlying culprit. This, among several other reasons, is why I really don't recommend ed25519 be used for anything.

At any rate, I think it's reasonable to make the behavior in regards to being more strict match between them.

EDIT: I should also note we'll need to be extremely careful here to ensure no consensus semantics are broken by such a change since, while I believe ed25519 pubkeys that are not 32 bytes are prevented elsewhere, I'm not 100% sure of that without digging into the code and that makes a big difference.

I've confirmed that no consensus semantics are broken and made the aforementioned change in #2869.