5
5
6
6
using System ;
7
7
using System . Diagnostics ;
8
+ using System . Runtime . Serialization ;
8
9
using System . Security . Cryptography ;
9
10
10
11
namespace SimpleBase ;
@@ -28,9 +29,9 @@ public sealed class Base58(Base58Alphabet alphabet) : DividingCoder<Base58Alphab
28
29
const int reductionFactor = 733 ;
29
30
30
31
const int maxCheckPayloadLength = 256 ;
32
+ const int minCheckDecodedBufferSize = 5 ;
31
33
const int sha256Bytes = 32 ;
32
34
const int sha256DigestBytes = 4 ;
33
-
34
35
static readonly Lazy < Base58 > bitcoin = new ( ( ) => new Base58 ( Base58Alphabet . Bitcoin ) ) ;
35
36
static readonly Lazy < Base58 > ripple = new ( ( ) => new Base58 ( Base58Alphabet . Ripple ) ) ;
36
37
static readonly Lazy < Base58 > flickr = new ( ( ) => new Base58 ( Base58Alphabet . Flickr ) ) ;
@@ -87,17 +88,57 @@ public string EncodeCheck(ReadOnlySpan<byte> payload, byte version)
87
88
throw new ArgumentException ( $ "Payload length { payload . Length } is greater than { maxCheckPayloadLength } ", nameof ( payload ) ) ;
88
89
}
89
90
90
- int versionPlusPayloadLen = payload . Length + 1 ;
91
- int outputLen = versionPlusPayloadLen + sha256DigestBytes ;
91
+ ReadOnlySpan < byte > prefix = [ version ] ;
92
+ return EncodeCheck ( payload , prefix ) ;
93
+ }
94
+
95
+ /// <summary>
96
+ /// Generate a Base58Check string out of a prefix buffer and payload.
97
+ /// </summary>
98
+ /// <param name="payload">Address data.</param>
99
+ /// <param name="prefix">Prefix buffer.</param>
100
+ /// <returns>Base58Check address.</returns>
101
+ /// <exception cref="ArgumentException">If <paramref name="payload"/> is empty.</exception>
102
+ public string EncodeCheck ( ReadOnlySpan < byte > payload , ReadOnlySpan < byte > prefix )
103
+ {
104
+ int totalLength = prefix . Length + payload . Length ;
105
+ int outputLen = totalLength + sha256DigestBytes ;
92
106
Span < byte > output = ( outputLen < Bits . SafeStackMaxAllocSize ) ? stackalloc byte [ outputLen ] : new byte [ outputLen ] ;
93
- output [ 0 ] = version ;
94
- payload . CopyTo ( output [ 1 ..] ) ;
107
+ prefix . CopyTo ( output ) ;
108
+ payload . CopyTo ( output [ prefix . Length ..] ) ;
95
109
Span < byte > sha256 = stackalloc byte [ sha256Bytes ] ;
96
- computeDoubleSha256 ( output [ ..versionPlusPayloadLen ] , sha256 ) ;
97
- sha256 [ ..sha256DigestBytes ] . CopyTo ( output [ versionPlusPayloadLen ..] ) ;
110
+ computeDoubleSha256 ( output [ ..totalLength ] , sha256 ) ;
111
+ sha256 [ ..sha256DigestBytes ] . CopyTo ( output [ totalLength ..] ) ;
98
112
return Encode ( output ) ;
99
113
}
100
114
115
+ /// <summary>
116
+ /// Generate a Base58Check string out of a prefix buffer and payload
117
+ /// by skipping leading zeroes in <paramref name="payload"/>.
118
+ /// Platforms like Tezos expects this behavior.
119
+ /// </summary>
120
+ /// <param name="payload">Address data.</param>
121
+ /// <param name="prefix">Prefix buffer.</param>
122
+ /// <returns>Base58Check address.</returns>
123
+ /// <exception cref="ArgumentException">
124
+ /// If <paramref name="payload"/> is empty or only contains zeroes.
125
+ /// </exception>
126
+ public string EncodeCheckSkipZeroes ( ReadOnlySpan < byte > payload , ReadOnlySpan < byte > prefix )
127
+ {
128
+ int firstNonZeroIndex = payload . IndexOfAnyExcept ( ( byte ) 0 ) ;
129
+ if ( firstNonZeroIndex < 0 )
130
+ {
131
+ throw new ArgumentException ( "Payload cannot be empty or all zeroes" , nameof ( payload ) ) ;
132
+ }
133
+
134
+ if ( firstNonZeroIndex > 0 )
135
+ {
136
+ payload = payload [ firstNonZeroIndex ..] ;
137
+ }
138
+
139
+ return EncodeCheck ( payload , prefix ) ;
140
+ }
141
+
101
142
/// <summary>
102
143
/// Try to decode and verify a Base58Check address.
103
144
/// </summary>
@@ -111,28 +152,47 @@ public bool TryDecodeCheck(
111
152
Span < byte > payload ,
112
153
out byte version ,
113
154
out int bytesWritten )
155
+ {
156
+ Span < byte > versionBuffer = stackalloc byte [ 1 ] ;
157
+ bool result = TryDecodeCheck ( address , payload , versionBuffer , out bytesWritten ) ;
158
+ version = versionBuffer [ 0 ] ;
159
+ return result ;
160
+ }
161
+
162
+ /// <summary>
163
+ /// Try to decode and verify a Base58Check address.
164
+ /// </summary>
165
+ /// <param name="address">Address string.</param>
166
+ /// <param name="payload">Output address buffer.</param>
167
+ /// <param name="prefix">Prefix decoded, must have the exact size of the expected prefix.</param>
168
+ /// <param name="payloadBytesWritten">Number of bytes written in the output payload.</param>
169
+ /// <returns>True if address was decoded successfully and passed validation. False, otherwise.</returns>
170
+ public bool TryDecodeCheck (
171
+ ReadOnlySpan < char > address ,
172
+ Span < byte > payload ,
173
+ Span < byte > prefix ,
174
+ out int payloadBytesWritten )
114
175
{
115
176
Span < byte > buffer = stackalloc byte [ maxCheckPayloadLength + sha256DigestBytes + 1 ] ;
116
- if ( ! TryDecode ( address , buffer , out bytesWritten ) || bytesWritten < 5 )
177
+ if ( ! TryDecode ( address , buffer , out int decodedBufferSize ) || decodedBufferSize < minCheckDecodedBufferSize )
117
178
{
118
- version = 0 ;
179
+ payloadBytesWritten = 0 ;
119
180
return false ;
120
181
}
121
182
122
- buffer = buffer [ ..bytesWritten ] ;
123
- version = buffer [ 0 ] ;
183
+ buffer = buffer [ ..decodedBufferSize ] ;
184
+ buffer [ .. prefix . Length ] . CopyTo ( prefix ) ;
124
185
Span < byte > sha256 = stackalloc byte [ sha256Bytes ] ;
125
186
computeDoubleSha256 ( buffer [ ..^ sha256DigestBytes ] , sha256 ) ;
126
187
if ( ! sha256 [ ..sha256DigestBytes ] . SequenceEqual ( buffer [ ^ sha256DigestBytes ..] ) )
127
188
{
128
- version = 0 ;
189
+ payloadBytesWritten = 0 ;
129
190
return false ;
130
191
}
131
192
132
- var finalBuffer = buffer [ 1 ..^ sha256DigestBytes ] ;
133
- version = buffer [ 0 ] ;
193
+ var finalBuffer = buffer [ prefix . Length ..^ sha256DigestBytes ] ;
134
194
finalBuffer . CopyTo ( payload ) ;
135
- bytesWritten = finalBuffer . Length ;
195
+ payloadBytesWritten = finalBuffer . Length ;
136
196
return true ;
137
197
}
138
198
0 commit comments