Skip to content

Commit e0a99c4

Browse files
committed
feat: asset fingerprint
Fixes #297
1 parent fbb7073 commit e0a99c4

File tree

3 files changed

+130
-12
lines changed

3 files changed

+130
-12
lines changed

ledger/block.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,6 @@ type BlockHeader interface {
3434
Cbor() []byte
3535
}
3636

37-
type Blake2b256 [32]byte
38-
39-
func (b Blake2b256) String() string {
40-
return hex.EncodeToString([]byte(b[:]))
41-
}
42-
43-
type Blake2b224 [28]byte
44-
45-
func (b Blake2b224) String() string {
46-
return hex.EncodeToString([]byte(b[:]))
47-
}
48-
4937
func NewBlockFromCbor(blockType uint, data []byte) (Block, error) {
5038
switch blockType {
5139
case BLOCK_TYPE_BYRON_EBB:

ledger/common.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,64 @@
1515
package ledger
1616

1717
import (
18+
"encoding/hex"
1819
"fmt"
1920

2021
"github.com/blinklabs-io/gouroboros/cbor"
2122
"github.com/blinklabs-io/gouroboros/internal/base58"
2223
"github.com/blinklabs-io/gouroboros/internal/bech32"
24+
25+
"golang.org/x/crypto/blake2b"
2326
)
2427

28+
type Blake2b256 [32]byte
29+
30+
func NewBlake2b256(data []byte) Blake2b256 {
31+
b := Blake2b256{}
32+
copy(b[:], data)
33+
return b
34+
}
35+
36+
func (b Blake2b256) String() string {
37+
return hex.EncodeToString([]byte(b[:]))
38+
}
39+
40+
func (b Blake2b256) Bytes() []byte {
41+
return b[:]
42+
}
43+
44+
type Blake2b224 [28]byte
45+
46+
func NewBlake2b224(data []byte) Blake2b224 {
47+
b := Blake2b224{}
48+
copy(b[:], data)
49+
return b
50+
}
51+
52+
func (b Blake2b224) String() string {
53+
return hex.EncodeToString([]byte(b[:]))
54+
}
55+
56+
func (b Blake2b224) Bytes() []byte {
57+
return b[:]
58+
}
59+
60+
type Blake2b160 [20]byte
61+
62+
func NewBlake2b160(data []byte) Blake2b160 {
63+
b := Blake2b160{}
64+
copy(b[:], data)
65+
return b
66+
}
67+
68+
func (b Blake2b160) String() string {
69+
return hex.EncodeToString([]byte(b[:]))
70+
}
71+
72+
func (b Blake2b160) Bytes() []byte {
73+
return b[:]
74+
}
75+
2576
// MultiAsset represents a collection of policies, assets, and quantities. It's used for
2677
// TX outputs (uint64) and TX asset minting (int64 to allow for negative values for burning)
2778
type MultiAsset[T int64 | uint64] struct {
@@ -65,6 +116,40 @@ func (m *MultiAsset[T]) Asset(policyId Blake2b224, assetName []byte) T {
65116
return policy[cbor.ByteString(assetName)]
66117
}
67118

119+
type AssetFingerprint struct {
120+
policyId []byte
121+
assetName []byte
122+
}
123+
124+
func NewAssetFingerprint(policyId []byte, assetName []byte) AssetFingerprint {
125+
return AssetFingerprint{
126+
policyId: policyId,
127+
assetName: assetName,
128+
}
129+
}
130+
131+
func (a AssetFingerprint) Hash() Blake2b160 {
132+
// We can ignore the error return here because our fixed size/key arguments will
133+
// never trigger an error
134+
tmpHash, _ := blake2b.New(20, nil)
135+
tmpHash.Write(a.policyId)
136+
tmpHash.Write(a.assetName)
137+
return NewBlake2b160(tmpHash.Sum(nil))
138+
}
139+
140+
func (a AssetFingerprint) String() string {
141+
// Convert data to base32 and encode as bech32
142+
convData, err := bech32.ConvertBits(a.Hash().Bytes(), 8, 5, true)
143+
if err != nil {
144+
panic(fmt.Sprintf("unexpected error converting data to base32: %s", err))
145+
}
146+
encoded, err := bech32.Encode("asset", convData)
147+
if err != nil {
148+
panic(fmt.Sprintf("unexpected error encoding data as bech32: %s", err))
149+
}
150+
return encoded
151+
}
152+
68153
const (
69154
addressHeaderTypeMask = 0xF0
70155
addressHeaderNetworkMask = 0x0F

ledger/common_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package ledger
2+
3+
import (
4+
"encoding/hex"
5+
"testing"
6+
)
7+
8+
func TestAssetFingerprint(t *testing.T) {
9+
testDefs := []struct {
10+
policyIdHex string
11+
assetNameHex string
12+
expectedFingerprint string
13+
}{
14+
// NOTE: these test defs were created from a random sampling of recent assets on cexplorer.io
15+
{
16+
policyIdHex: "29a8fb8318718bd756124f0c144f56d4b4579dc5edf2dd42d669ac61",
17+
assetNameHex: "6675726e697368613239686e",
18+
expectedFingerprint: "asset1jdu2xcrwlqsjqqjger6kj2szddz8dcpvcg4ksz",
19+
},
20+
{
21+
policyIdHex: "eaf8042c1d8203b1c585822f54ec32c4c1bb4d3914603e2cca20bbd5",
22+
assetNameHex: "426f7764757261436f6e63657074733638",
23+
expectedFingerprint: "asset1kp7hdhqc7chmyqvtqrsljfdrdt6jz8mg5culpe",
24+
},
25+
{
26+
policyIdHex: "cf78aeb9736e8aa94ce8fab44da86b522fa9b1c56336b92a28420525",
27+
assetNameHex: "363438346330393264363164373033656236333233346461",
28+
expectedFingerprint: "asset1rx3cnlsvh3udka56wyqyed3u695zd5q2jck2yd",
29+
},
30+
}
31+
for _, test := range testDefs {
32+
policyIdBytes, err := hex.DecodeString(test.policyIdHex)
33+
if err != nil {
34+
t.Fatalf("failed to decode policy ID hex: %s", err)
35+
}
36+
assetNameBytes, err := hex.DecodeString(test.assetNameHex)
37+
if err != nil {
38+
t.Fatalf("failed to decode asset name hex: %s", err)
39+
}
40+
fp := NewAssetFingerprint(policyIdBytes, assetNameBytes)
41+
if fp.String() != test.expectedFingerprint {
42+
t.Fatalf("asset fingerprint did not match expected value, got: %s, wanted: %s", fp.String(), test.expectedFingerprint)
43+
}
44+
}
45+
}

0 commit comments

Comments
 (0)