Skip to content

Commit 43948b5

Browse files
feat(xrpl): AuthorizeChannel to authorize a payment channel.
1 parent 62ea18a commit 43948b5

File tree

3 files changed

+203
-0
lines changed

3 files changed

+203
-0
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
#### xrpl
13+
14+
- `AuthorizeChannel` to authorize a payment channel.
15+
1016
### Refactored
1117

18+
#### xrpl
19+
1220
- `TxResponse` `Meta` field type changed to `TxMetadataBuilder`, enabling custom parsing for specific transactions metadata such as `Payment`, `NFTokenMint`, etc.
1321

1422
## [v0.1.13]

xrpl/wallet/authorize_channel.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package wallet
2+
3+
import (
4+
"encoding/hex"
5+
6+
binarycodec "github.com/Peersyst/xrpl-go/binary-codec"
7+
"github.com/Peersyst/xrpl-go/keypairs"
8+
)
9+
10+
// AuthorizeChannel returns a signature authorizing the redemption of a specific
11+
// amount of XRP from a payment channel.
12+
//
13+
// channelID identifies the payment channel.
14+
// amount is the amount to redeem, expressed in drops.
15+
//
16+
// Returns the signature or an error if the signature cannot be created.
17+
func AuthorizeChannel(channelID, amount string, wallet *Wallet) (string, error) {
18+
encodedData, err := binarycodec.EncodeForSigningClaim(map[string]any{
19+
"Channel": channelID,
20+
"Amount": amount,
21+
})
22+
if err != nil {
23+
return "", err
24+
}
25+
hexData, err := hex.DecodeString(encodedData)
26+
if err != nil {
27+
return "", err
28+
}
29+
signedData, err := keypairs.Sign(string(hexData), wallet.PrivateKey)
30+
if err != nil {
31+
return "", err
32+
}
33+
return signedData, nil
34+
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package wallet
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestAuthorizeChannel(t *testing.T) {
10+
// secp256k1 wallet
11+
secpWallet, err := FromSeed("snGHNrPbHrdUcszeuDEigMdC1Lyyd", "")
12+
require.NoError(t, err)
13+
14+
// ed25519 wallet
15+
edWallet, err := FromSeed("sEdSuqBPSQaood2DmNYVkwWTn1oQTj2", "")
16+
require.NoError(t, err)
17+
18+
channelID := "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3"
19+
amount := "1000000"
20+
21+
tc := []struct {
22+
name string
23+
wallet *Wallet
24+
channelID string
25+
amount string
26+
expectedSig string
27+
}{
28+
{
29+
name: "pass - succeeds with secp256k1 seed",
30+
wallet: &secpWallet,
31+
channelID: channelID,
32+
amount: amount,
33+
expectedSig: "304402204E7052F33DDAFAAA55C9F5B132A5E50EE95B2CF68C0902F61DFE77299BC893740220353640B951DCD24371C16868B3F91B78D38B6F3FD1E826413CDF891FA8250AAC",
34+
},
35+
{
36+
name: "pass - succeeds with ed25519 seed",
37+
wallet: &edWallet,
38+
channelID: channelID,
39+
amount: amount,
40+
expectedSig: "7E1C217A3E4B3C107B7A356E665088B4FBA6464C48C58267BEF64975E3375EA338AE22E6714E3F5E734AE33E6B97AAD59058E1E196C1F92346FC1498D0674404",
41+
},
42+
{
43+
name: "fail - fails with invalid channel ID format",
44+
wallet: &secpWallet,
45+
channelID: "invalid-id",
46+
amount: amount,
47+
expectedSig: "",
48+
},
49+
{
50+
name: "fail - fails with invalid amount format",
51+
wallet: &secpWallet,
52+
channelID: channelID,
53+
amount: "invalid-amount",
54+
expectedSig: "",
55+
},
56+
}
57+
58+
for _, tt := range tc {
59+
t.Run(tt.name, func(t *testing.T) {
60+
signature, err := AuthorizeChannel(tt.channelID, tt.amount, tt.wallet)
61+
if err == nil && tt.expectedSig != "" {
62+
require.NoError(t, err)
63+
require.Equal(t, tt.expectedSig, signature)
64+
} else {
65+
require.Error(t, err)
66+
require.Empty(t, signature)
67+
}
68+
})
69+
}
70+
}
71+
72+
func TestAuthorizeChannel_EdgeCases(t *testing.T) {
73+
// secp256k1 wallet
74+
secpWallet, err := FromSeed("snGHNrPbHrdUcszeuDEigMdC1Lyyd", "")
75+
require.NoError(t, err)
76+
77+
// ed25519 wallet
78+
edWallet, err := FromSeed("sEdSuqBPSQaood2DmNYVkwWTn1oQTj2", "")
79+
require.NoError(t, err)
80+
81+
validChannelID := "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3"
82+
validAmount := "1000000"
83+
84+
tc := []struct {
85+
name string
86+
wallet *Wallet
87+
channelID string
88+
amount string
89+
expectError bool
90+
}{
91+
{
92+
name: "pass - different amounts with secp256k1",
93+
wallet: &secpWallet,
94+
channelID: validChannelID,
95+
amount: "5000000",
96+
expectError: false,
97+
},
98+
{
99+
name: "pass - different amounts with ed25519",
100+
wallet: &edWallet,
101+
channelID: validChannelID,
102+
amount: "5000000",
103+
expectError: false,
104+
},
105+
{
106+
name: "pass - zero amount",
107+
wallet: &secpWallet,
108+
channelID: validChannelID,
109+
amount: "0",
110+
expectError: false,
111+
},
112+
{
113+
name: "fail - empty channel ID",
114+
wallet: &secpWallet,
115+
channelID: "",
116+
amount: validAmount,
117+
expectError: true,
118+
},
119+
{
120+
name: "fail - empty amount",
121+
wallet: &secpWallet,
122+
channelID: validChannelID,
123+
amount: "",
124+
expectError: true,
125+
},
126+
{
127+
name: "fail - channel ID too short",
128+
wallet: &secpWallet,
129+
channelID: "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB",
130+
amount: validAmount,
131+
expectError: true,
132+
},
133+
{
134+
name: "fail - channel ID too long",
135+
wallet: &secpWallet,
136+
channelID: "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3A",
137+
amount: validAmount,
138+
expectError: true,
139+
},
140+
{
141+
name: "fail - channel ID with invalid hex characters",
142+
wallet: &secpWallet,
143+
channelID: "5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDBG",
144+
amount: validAmount,
145+
expectError: true,
146+
},
147+
}
148+
149+
for _, tt := range tc {
150+
t.Run(tt.name, func(t *testing.T) {
151+
signature, err := AuthorizeChannel(tt.channelID, tt.amount, tt.wallet)
152+
if tt.expectError {
153+
require.Error(t, err)
154+
require.Empty(t, signature)
155+
} else {
156+
require.NoError(t, err)
157+
require.NotEmpty(t, signature)
158+
}
159+
})
160+
}
161+
}

0 commit comments

Comments
 (0)