Skip to content

Commit

Permalink
Merge pull request #111 from kcalvinalvin/2024-01-09-tagged-hashes-fo…
Browse files Browse the repository at this point in the history
…r-leafdata

chainhash, wire: implement tagged hashes for utreexo leaves
  • Loading branch information
kcalvinalvin authored Jan 10, 2024
2 parents e9b0118 + 89a4121 commit 979273d
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 9 deletions.
31 changes: 31 additions & 0 deletions chaincfg/chainhash/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ package chainhash

import (
"crypto/sha256"
"crypto/sha512"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"math"
)

Expand Down Expand Up @@ -58,6 +60,14 @@ var (
string(TagTapBranch): sha256.Sum256(TagTapBranch),
string(TagTapTweak): sha256.Sum256(TagTapTweak),
}

// TagUtreexoV1 is the tag used by utreexo v1 serialized hashes to
// generate the leaf hashes to be committed into the accumulator.
TagUtreexoV1 = []byte("UtreexoV1")

precomputedUtreexoTags = map[string][64]byte{
string(TagUtreexoV1): sha512.Sum512(TagUtreexoV1),
}
)

// ErrHashStrSize describes an error that indicates the caller specified a hash
Expand Down Expand Up @@ -173,6 +183,27 @@ func TaggedHash(tag []byte, msgs ...[]byte) *Hash {
return hash
}

// TaggedHash512_256 implements a tagged hash scheme for utreexo leaves. We use
// sha-512_256 to bind a message hash to a specific context using a tag:
// sha512_256(sha512(tag) || sha512(tag) || leafdata).
func TaggedHash512_256(tag []byte, serialize func(io.Writer)) *Hash {
// Check to see if we've already pre-computed the hash of the tag. If
// so then this'll save us an extra sha512 hash.
shaTag, ok := precomputedUtreexoTags[string(tag)]
if !ok {
shaTag = sha512.Sum512(tag)
}

// h = sha512_256(sha512(tag) || sha512(tag) || leafdata)
h := sha512.New512_256()
h.Write(shaTag[:])
h.Write(shaTag[:])
serialize(h)

taggedHash := h.Sum(nil)
return (*Hash)(taggedHash)
}

// NewHashFromStr creates a Hash from a hash string. The string should be
// the hexadecimal string of a byte-reversed hash, but any missing characters
// result in zero padding at the end of the Hash.
Expand Down
31 changes: 31 additions & 0 deletions chaincfg/chainhash/hash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ package chainhash

import (
"bytes"
"crypto/sha512"
"encoding/hex"
"encoding/json"
"io"
"reflect"
"testing"
)
Expand Down Expand Up @@ -243,3 +245,32 @@ func TestPackedHashes(t *testing.T) {
}
}
}

func TestTaggedHash512_256(t *testing.T) {
tests := []struct {
tagStr string
msg string
}{
{tagStr: "UtreexoV1", msg: "hi"},
{tagStr: "UtreexoV1", msg: "12354561654"},
{tagStr: "hi", msg: "12354561654"},
}

for _, test := range tests {
var serialized bytes.Buffer
tag, found := precomputedUtreexoTags[test.tagStr]
if !found {
tag = sha512.Sum512([]byte(test.tagStr))
}
serialized.Write(tag[:])
serialized.Write(tag[:])
serialized.Write([]byte(test.msg))

expect := sha512.Sum512_256(serialized.Bytes())

got := TaggedHash512_256([]byte(test.tagStr), func(w io.Writer) { w.Write([]byte(test.msg)) })
if !bytes.Equal(got[:], expect[:]) {
t.Fatalf("expected %s, got %s", hex.EncodeToString(expect[:]), hex.EncodeToString(got[:]))
}
}
}
14 changes: 5 additions & 9 deletions wire/leaf.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package wire

import (
"bytes"
"crypto/sha512"
"encoding/hex"
"encoding/json"
"fmt"
Expand Down Expand Up @@ -115,14 +114,11 @@ func (l *LeafData) UnmarshalJSON(data []byte) error {

// LeafHash concats and hashes all the data in LeafData.
func (l *LeafData) LeafHash() [32]byte {
digest := sha512.New512_256()
l.Serialize(digest)

// TODO go 1.17 support slice to array conversion so we
// can avoid this extra copy.
hash := [32]byte{}
copy(hash[:], digest.Sum(nil))
return hash
return *chainhash.TaggedHash512_256(chainhash.TagUtreexoV1,
func(w io.Writer) {
l.Serialize(w)
},
)
}

// ToString turns a LeafData into a string for logging.
Expand Down
65 changes: 65 additions & 0 deletions wire/leaf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package wire

import (
"bytes"
"crypto/sha512"
"encoding/hex"
"encoding/json"
"fmt"
Expand Down Expand Up @@ -630,3 +631,67 @@ func TestIsCompact(t *testing.T) {
}
}
}

func TestLeafHash(t *testing.T) {
t.Parallel()

tests := []struct {
name string
ld LeafData
}{
{
name: "Testnet3 tx 061bb0bf... from block 1600000",
ld: LeafData{
BlockHash: *newHashFromStr("00000000000172ff8a4e14441512072bacaf8d38b995a3fcd2f8435efc61717d"),
OutPoint: OutPoint{
Hash: *newHashFromStr("061bb0bf3a1b9df13773da06bf92920394887a9c2b8b8772ac06be4e077df5eb"),
Index: 10,
},
Amount: 200000,
PkScript: hexToBytes("a914e8d74935cfa223f9750a32b18d609cba17a5c3fe87"),
Height: 1599255,
IsCoinBase: false,
},
},
{
name: "Mainnet coinbase tx fa201b65... from block 573123",
ld: LeafData{
BlockHash: *newHashFromStr("000000000000000000278eb9386b4e70b850a4ec21907af3a27f50330b7325aa"),
OutPoint: OutPoint{
Hash: *newHashFromStr("fa201b650eef761f5701afbb610e4a211b86985da4745aec3ac0f4b7a8e2c8d2"),
Index: 0,
},
Amount: 1315080370,
PkScript: hexToBytes("76a9142cc2b87a28c8a097f48fcc1d468ced6e7d39958d88ac"),
Height: 573123,
IsCoinBase: true,
},
},
}

// Just a hash func for sanity checking here.
hashFunc := func(ld LeafData) [32]byte {
shaTag := sha512.Sum512(chainhash.TagUtreexoV1)
preimage := make([]byte, 0, (64*2)+ld.SerializeSize())
preimage = append(preimage, shaTag[:]...)
preimage = append(preimage, shaTag[:]...)
var buf bytes.Buffer
err := ld.Serialize(&buf)
if err != nil {
t.Fatal(err)
}
preimage = append(preimage, buf.Bytes()...)

return sha512.Sum512_256(preimage)
}

for _, test := range tests {
got := test.ld.LeafHash()
expect := hashFunc(test.ld)
if got != expect {
t.Fatalf("expect %s but got %s",
hex.EncodeToString(expect[:]),
hex.EncodeToString(got[:]))
}
}
}

0 comments on commit 979273d

Please sign in to comment.