Skip to content

Commit 5eb88b6

Browse files
Merge pull request #189 from XRPLF/binary-codec/feat/update-definitions
feat(binary-codec): update definitions
2 parents ca24f8b + c04dfe9 commit 5eb88b6

File tree

148 files changed

+13562
-2132
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

148 files changed

+13562
-2132
lines changed

.ci-config/rippled.cfg

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,7 @@ r.ripple.com 51235
9191
# 3. Click on each amendment to get their Amendment ID and name to add to this list manually.
9292
# You will likely update the list with all amendments from a new release of rippled all at once.
9393

94-
# To get the list of amendments on a network (e.g. devnet) follow the steps in xrpl.js's CONTRIBUTING.md for "Updating the Docker container".
95-
# https://github.com/XRPLF/xrpl.js/blob/main/CONTRIBUTING.md
96-
# (Running the script `getNewAmendments.js` should help you identify any new amendments that should be added.)
97-
#
98-
# Note: The version of rippled you use this config with must have an implementation for the amendments you attempt to enable or it will crash.
99-
# If you need the version of rippled to be more up to date, you may need to make a comment on this repo: https://github.com/WietseWind/docker-rippled
94+
10095

10196
[features]
10297
# Devnet amendments as of June 28th, 2023

address-codec/codec.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,18 @@ func EncodeClassicAddressFromPublicKeyHex(pubkeyhex string) (string, error) {
8888

8989
// DecodeClassicAddressToAccountID returns the prefix and accountID byte slice from a classic address.
9090
func DecodeClassicAddressToAccountID(cAddress string) (typePrefix, accountID []byte, err error) {
91-
if len(DecodeBase58(cAddress)) != 25 {
91+
// Use Base58CheckDecode to validate checksum
92+
decoded, err := Base58CheckDecode(cAddress)
93+
if err != nil {
94+
return nil, nil, ErrInvalidClassicAddress
95+
}
96+
97+
// Expected length is 21 bytes (1 prefix + 20 accountID) after removing 4-byte checksum
98+
if len(decoded) != 21 {
9299
return nil, nil, ErrInvalidClassicAddress
93100
}
94101

95-
return DecodeBase58(cAddress)[:1], DecodeBase58(cAddress)[1:21], nil
102+
return decoded[:1], decoded[1:21], nil
96103
}
97104

98105
// EncodeAccountIDToClassicAddress returns the classic address encoding of the accountId.

address-codec/compat_test.go

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
package addresscodec
2+
3+
import (
4+
"encoding/hex"
5+
"encoding/json"
6+
"os"
7+
"strings"
8+
"testing"
9+
10+
"github.com/Peersyst/xrpl-go/pkg/crypto"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
// Fixtures represents the structure of address-fixtures.json
15+
type Fixtures struct {
16+
EncodeDecodeAccountID []EncodeDecodeTest `json:"encodeDecodeAccountID"`
17+
EncodeDecodeNodePublic []EncodeDecodeTest `json:"encodeDecodeNodePublic"`
18+
EncodeDecodeAccountPublic []EncodeDecodeTest `json:"encodeDecodeAccountPublic"`
19+
Seeds []SeedTest `json:"seeds"`
20+
ValidClassicAddresses []string `json:"validClassicAddresses"`
21+
InvalidClassicAddresses []string `json:"invalidClassicAddresses"`
22+
XAddresses []XAddressTest `json:"xAddresses"`
23+
InvalidXAddresses []InvalidXAddress `json:"invalidXAddresses"`
24+
CodecTests []CodecTest `json:"codecTests"`
25+
}
26+
27+
type EncodeDecodeTest struct {
28+
Hex string `json:"hex"`
29+
Base58 string `json:"base58"`
30+
}
31+
32+
type SeedTest struct {
33+
Hex string `json:"hex"`
34+
Base58 string `json:"base58"`
35+
Type string `json:"type"`
36+
}
37+
38+
type XAddressTest struct {
39+
ClassicAddress string `json:"classicAddress"`
40+
Tag *int64 `json:"tag"` // pointer to handle null
41+
MainnetAddress string `json:"mainnetAddress"`
42+
TestnetAddress string `json:"testnetAddress"`
43+
}
44+
45+
type InvalidXAddress struct {
46+
Address string `json:"address"`
47+
Error string `json:"error"`
48+
}
49+
50+
type CodecTest struct {
51+
Input string `json:"input"`
52+
Version int `json:"version"`
53+
ExpectedLength int `json:"expectedLength"`
54+
Encoded string `json:"encoded"`
55+
}
56+
57+
func loadFixtures(t *testing.T) *Fixtures {
58+
data, err := os.ReadFile("testdata/fixtures/address-fixtures.json")
59+
require.NoError(t, err, "Failed to read fixtures file")
60+
61+
var fixtures Fixtures
62+
err = json.Unmarshal(data, &fixtures)
63+
require.NoError(t, err, "Failed to parse fixtures JSON")
64+
65+
return &fixtures
66+
}
67+
68+
// TestCompat_EncodeDecodeAccountID tests encoding and decoding of AccountIDs
69+
func TestCompat_EncodeDecodeAccountID(t *testing.T) {
70+
fixtures := loadFixtures(t)
71+
72+
for _, tc := range fixtures.EncodeDecodeAccountID {
73+
t.Run(tc.Base58, func(t *testing.T) {
74+
// Test encoding
75+
hexBytes, err := hex.DecodeString(tc.Hex)
76+
require.NoError(t, err)
77+
78+
encoded, err := EncodeAccountIDToClassicAddress(hexBytes)
79+
require.NoError(t, err)
80+
require.Equal(t, tc.Base58, encoded, "Encoding mismatch for hex: %s", tc.Hex)
81+
82+
// Test decoding
83+
_, decoded, err := DecodeClassicAddressToAccountID(tc.Base58)
84+
require.NoError(t, err)
85+
require.Equal(t, strings.ToUpper(tc.Hex), strings.ToUpper(hex.EncodeToString(decoded)), "Decoding mismatch for base58: %s", tc.Base58)
86+
})
87+
}
88+
}
89+
90+
// TestCompat_EncodeDecodeNodePublic tests encoding and decoding of NodePublic keys
91+
func TestCompat_EncodeDecodeNodePublic(t *testing.T) {
92+
fixtures := loadFixtures(t)
93+
94+
for _, tc := range fixtures.EncodeDecodeNodePublic {
95+
t.Run(tc.Base58, func(t *testing.T) {
96+
// Test encoding
97+
hexBytes, err := hex.DecodeString(tc.Hex)
98+
require.NoError(t, err)
99+
100+
encoded, err := EncodeNodePublicKey(hexBytes)
101+
require.NoError(t, err)
102+
require.Equal(t, tc.Base58, encoded, "Encoding mismatch for hex: %s", tc.Hex)
103+
104+
// Test decoding
105+
decoded, err := DecodeNodePublicKey(tc.Base58)
106+
require.NoError(t, err)
107+
require.Equal(t, strings.ToUpper(tc.Hex), strings.ToUpper(hex.EncodeToString(decoded)), "Decoding mismatch for base58: %s", tc.Base58)
108+
})
109+
}
110+
}
111+
112+
// TestCompat_EncodeDecodeAccountPublic tests encoding and decoding of AccountPublic keys
113+
func TestCompat_EncodeDecodeAccountPublic(t *testing.T) {
114+
fixtures := loadFixtures(t)
115+
116+
for _, tc := range fixtures.EncodeDecodeAccountPublic {
117+
t.Run(tc.Base58, func(t *testing.T) {
118+
// Test encoding
119+
hexBytes, err := hex.DecodeString(tc.Hex)
120+
require.NoError(t, err)
121+
122+
encoded, err := EncodeAccountPublicKey(hexBytes)
123+
require.NoError(t, err)
124+
require.Equal(t, tc.Base58, encoded, "Encoding mismatch for hex: %s", tc.Hex)
125+
126+
// Test decoding
127+
decoded, err := DecodeAccountPublicKey(tc.Base58)
128+
require.NoError(t, err)
129+
require.Equal(t, strings.ToUpper(tc.Hex), strings.ToUpper(hex.EncodeToString(decoded)), "Decoding mismatch for base58: %s", tc.Base58)
130+
})
131+
}
132+
}
133+
134+
// TestCompat_EncodeSeed tests seed encoding
135+
func TestCompat_EncodeSeed(t *testing.T) {
136+
fixtures := loadFixtures(t)
137+
138+
for _, tc := range fixtures.Seeds {
139+
t.Run(tc.Base58, func(t *testing.T) {
140+
hexBytes, err := hex.DecodeString(tc.Hex)
141+
require.NoError(t, err)
142+
143+
var encoded string
144+
if tc.Type == "ed25519" {
145+
encoded, err = EncodeSeed(hexBytes, crypto.ED25519())
146+
} else {
147+
encoded, err = EncodeSeed(hexBytes, crypto.SECP256K1())
148+
}
149+
require.NoError(t, err)
150+
require.Equal(t, tc.Base58, encoded, "Seed encoding mismatch for hex: %s, type: %s", tc.Hex, tc.Type)
151+
})
152+
}
153+
}
154+
155+
// TestCompat_DecodeSeed tests seed decoding
156+
func TestCompat_DecodeSeed(t *testing.T) {
157+
fixtures := loadFixtures(t)
158+
159+
for _, tc := range fixtures.Seeds {
160+
t.Run(tc.Base58, func(t *testing.T) {
161+
decoded, cryptoType, err := DecodeSeed(tc.Base58)
162+
require.NoError(t, err)
163+
require.Equal(t, strings.ToUpper(tc.Hex), strings.ToUpper(hex.EncodeToString(decoded)), "Seed decoding mismatch for base58: %s", tc.Base58)
164+
165+
// Check type by comparing with known implementations
166+
if tc.Type == "ed25519" {
167+
_, ok := cryptoType.(crypto.ED25519CryptoAlgorithm)
168+
require.True(t, ok, "Expected ed25519 type for base58: %s", tc.Base58)
169+
} else {
170+
_, ok := cryptoType.(crypto.SECP256K1CryptoAlgorithm)
171+
require.True(t, ok, "Expected secp256k1 type for base58: %s", tc.Base58)
172+
}
173+
})
174+
}
175+
}
176+
177+
// TestCompat_IsValidClassicAddress tests classic address validation
178+
func TestCompat_IsValidClassicAddress(t *testing.T) {
179+
fixtures := loadFixtures(t)
180+
181+
for _, addr := range fixtures.ValidClassicAddresses {
182+
t.Run("valid_"+addr, func(t *testing.T) {
183+
require.True(t, IsValidClassicAddress(addr), "Expected %s to be valid", addr)
184+
})
185+
}
186+
187+
for _, addr := range fixtures.InvalidClassicAddresses {
188+
name := "invalid_" + addr
189+
if addr == "" {
190+
name = "invalid_empty"
191+
}
192+
t.Run(name, func(t *testing.T) {
193+
require.False(t, IsValidClassicAddress(addr), "Expected %s to be invalid", addr)
194+
})
195+
}
196+
}
197+
198+
// TestCompat_XAddressMainnet tests X-address encoding/decoding for mainnet
199+
func TestCompat_XAddressMainnet(t *testing.T) {
200+
fixtures := loadFixtures(t)
201+
202+
for _, tc := range fixtures.XAddresses {
203+
testName := tc.ClassicAddress
204+
if tc.Tag != nil {
205+
testName += "_tag_" + string(rune(*tc.Tag))
206+
} else {
207+
testName += "_no_tag"
208+
}
209+
t.Run("mainnet_"+testName, func(t *testing.T) {
210+
var tag uint32
211+
tagFlag := false
212+
if tc.Tag != nil {
213+
tag = uint32(*tc.Tag)
214+
tagFlag = true
215+
}
216+
217+
// Test classic -> X-address conversion
218+
xAddr, err := ClassicAddressToXAddress(tc.ClassicAddress, tag, tagFlag, false)
219+
require.NoError(t, err)
220+
require.Equal(t, tc.MainnetAddress, xAddr, "Classic to X-address conversion failed for %s", tc.ClassicAddress)
221+
222+
// Test X-address -> classic conversion
223+
classicAddr, decodedTag, isTestnet, err := XAddressToClassicAddress(tc.MainnetAddress)
224+
require.NoError(t, err)
225+
require.Equal(t, tc.ClassicAddress, classicAddr, "X-address to classic conversion failed for %s", tc.MainnetAddress)
226+
require.False(t, isTestnet, "Expected mainnet address")
227+
228+
if tc.Tag != nil {
229+
require.Equal(t, uint32(*tc.Tag), decodedTag, "Tag mismatch for %s", tc.MainnetAddress)
230+
}
231+
232+
// Test IsValidXAddress
233+
require.True(t, IsValidXAddress(tc.MainnetAddress), "Expected %s to be a valid X-address", tc.MainnetAddress)
234+
})
235+
}
236+
}
237+
238+
// TestCompat_XAddressTestnet tests X-address encoding/decoding for testnet
239+
func TestCompat_XAddressTestnet(t *testing.T) {
240+
fixtures := loadFixtures(t)
241+
242+
for _, tc := range fixtures.XAddresses {
243+
testName := tc.ClassicAddress
244+
if tc.Tag != nil {
245+
testName += "_tag"
246+
} else {
247+
testName += "_no_tag"
248+
}
249+
t.Run("testnet_"+testName, func(t *testing.T) {
250+
var tag uint32
251+
tagFlag := false
252+
if tc.Tag != nil {
253+
tag = uint32(*tc.Tag)
254+
tagFlag = true
255+
}
256+
257+
// Test classic -> X-address conversion
258+
xAddr, err := ClassicAddressToXAddress(tc.ClassicAddress, tag, tagFlag, true)
259+
require.NoError(t, err)
260+
require.Equal(t, tc.TestnetAddress, xAddr, "Classic to X-address conversion failed for %s", tc.ClassicAddress)
261+
262+
// Test X-address -> classic conversion
263+
classicAddr, decodedTag, isTestnet, err := XAddressToClassicAddress(tc.TestnetAddress)
264+
require.NoError(t, err)
265+
require.Equal(t, tc.ClassicAddress, classicAddr, "X-address to classic conversion failed for %s", tc.TestnetAddress)
266+
require.True(t, isTestnet, "Expected testnet address")
267+
268+
if tc.Tag != nil {
269+
require.Equal(t, uint32(*tc.Tag), decodedTag, "Tag mismatch for %s", tc.TestnetAddress)
270+
}
271+
272+
// Test IsValidXAddress
273+
require.True(t, IsValidXAddress(tc.TestnetAddress), "Expected %s to be a valid X-address", tc.TestnetAddress)
274+
})
275+
}
276+
}
277+
278+
// TestCompat_InvalidXAddresses tests that invalid X-addresses are properly rejected
279+
func TestCompat_InvalidXAddresses(t *testing.T) {
280+
fixtures := loadFixtures(t)
281+
282+
for _, tc := range fixtures.InvalidXAddresses {
283+
t.Run(tc.Address[:20], func(t *testing.T) {
284+
require.False(t, IsValidXAddress(tc.Address), "Expected %s to be invalid", tc.Address)
285+
286+
_, _, _, err := XAddressToClassicAddress(tc.Address)
287+
require.Error(t, err, "Expected error for invalid X-address: %s", tc.Address)
288+
})
289+
}
290+
}

address-codec/errors.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ var (
1313
// ErrInvalidSeed indicates an invalid seed; could not determine encoding algorithm.
1414
ErrInvalidSeed = errors.New("invalid seed; could not determine encoding algorithm")
1515
// ErrInvalidXAddress indicates an invalid x-address.
16-
ErrInvalidXAddress = errors.New("invalid x-address")
16+
ErrInvalidXAddress = errors.New("invalid X-address: bad prefix")
17+
// ErrUnsupportedXAddress indicates an unsupported x-address (e.g., 64-bit tag).
18+
ErrUnsupportedXAddress = errors.New("unsupported X-address")
1719
// ErrInvalidTag indicates an invalid tag.
1820
ErrInvalidTag = errors.New("invalid tag")
1921
// ErrInvalidAccountID indicates an invalid account ID.

0 commit comments

Comments
 (0)