-
Notifications
You must be signed in to change notification settings - Fork 10
/
account.go
95 lines (88 loc) · 2.17 KB
/
account.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package icrc
import (
"encoding/base32"
"encoding/binary"
"encoding/hex"
"fmt"
"github.com/aviate-labs/agent-go/principal"
"hash/crc32"
"strings"
)
var encoding = base32.StdEncoding.WithPadding(base32.NoPadding)
func trimLeadingZeros(str string) string {
for str[0] == '0' {
str = str[1:]
}
return str
}
type Account struct {
Owner principal.Principal
SubAccount *[32]byte
}
func Decode(s string) (Account, error) {
a := strings.Split(s, ".")
if len(a) == 1 {
owner, err := principal.Decode(s)
if err != nil {
return Account{}, err
}
return Account{
Owner: owner,
SubAccount: nil,
}, nil
}
if len(a) != 2 {
return Account{}, fmt.Errorf("invalid account identifier: %s", s)
}
p := strings.Split(a[0], "-")
b32crc := strings.ToUpper(p[len(p)-1])
owner, err := principal.Decode(strings.Join(p[:len(p)-1], "-"))
if err != nil {
return Account{}, err
}
if len(a[1]) == 0 || a[1][0] == '0' {
return Account{}, fmt.Errorf("invalid sub account: %s", a[1])
}
if len(a[1])%2 == 1 {
// Add leading zero if necessary.
a[1] = "0" + a[1]
}
subAccount, err := hex.DecodeString(a[1])
if err != nil {
return Account{}, err
}
for len(subAccount) < 32 {
subAccount = append([]byte{0}, subAccount...)
}
cs, err := encoding.DecodeString(b32crc)
if err != nil {
return Account{}, err
}
if len(cs) != 4 {
return Account{}, fmt.Errorf("invalid checksum size: %d", len(cs))
}
if crc32.ChecksumIEEE(append(owner.Raw, subAccount...)) != binary.BigEndian.Uint32(cs) {
return Account{}, fmt.Errorf("invalid checksum: %s", string(cs))
}
var subAccount32 [32]byte
copy(subAccount32[:], subAccount)
return Account{
Owner: owner,
SubAccount: &subAccount32,
}, nil
}
func (a Account) Encode() string {
if a.SubAccount == nil {
return a.Owner.String()
}
if *a.SubAccount == [32]byte{} {
return a.Owner.String()
}
cs := make([]byte, 4)
binary.BigEndian.PutUint32(cs, crc32.ChecksumIEEE(append(a.Owner.Raw, a.SubAccount[:]...)))
b32cs := strings.ToLower(encoding.EncodeToString(cs))
return a.Owner.String() + "-" + b32cs + "." + trimLeadingZeros(hex.EncodeToString(a.SubAccount[:]))
}
func (a Account) String() string {
return a.Encode()
}