Skip to content

Commit 8220903

Browse files
Make BULL wallet descriptors compatible with SamRock (#10)
Co-authored-by: FrancisPouliot <FrancisPouliot@users.noreply.github.com>
1 parent ccdee82 commit 8220903

1 file changed

Lines changed: 79 additions & 38 deletions

File tree

Plugins/SamRockProtocol/Controllers/ProtocolController.cs

Lines changed: 79 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,7 @@ private async Task<IActionResult> processSamRockProtocolRequest(SamRockProtocolR
147147
var key = SamRockProtocolKeys.BTC;
148148
try
149149
{
150-
// Parse output descriptor format and convert to NBXplorer format
151-
// Input: wpkh([8f681564/84'/0'/0']xpub...xxx/0/*)#8m68c9t7
152-
var descriptor = setupModel.BTC.Descriptor;
150+
var descriptor = NormalizeDescriptor(setupModel.BTC.Descriptor);
153151

154152
// Extract script type, fingerprint, derivation path, xpub, and address derivation suffix
155153
var match = Regex.Match(descriptor, @"^(\w+)\(\[([a-fA-F0-9]{8})/([^\]]+)\](xpub[^/\)]+)(/[^\)]+)?\)(?:#[a-zA-Z0-9]+)?$");
@@ -162,7 +160,7 @@ private async Task<IActionResult> processSamRockProtocolRequest(SamRockProtocolR
162160
{
163161
var scriptType = match.Groups[1].Value;
164162
var fingerprint = match.Groups[2].Value;
165-
var basePath = match.Groups[3].Value;
163+
var basePath = NormalizeDerivationPath(match.Groups[3].Value);
166164
var xpub = match.Groups[4].Value;
167165
var addressSuffix = match.Groups[5].Value; // e.g., "/0/*"
168166

@@ -197,43 +195,16 @@ private async Task<IActionResult> processSamRockProtocolRequest(SamRockProtocolR
197195
{
198196
try
199197
{
200-
// Parse LBTC output descriptor format and convert to NBXplorer format
201-
// Input: ct(slip77(blinding_key),elsh(wpkh([fingerprint/path]xpub...)))
202-
var descriptor = setupModel.LBTC.Descriptor;
203-
204-
// Extract slip77 blinding key, script type, fingerprint, derivation path, xpub, and address derivation suffix
205-
var match = Regex.Match(descriptor,
206-
@"^ct\(slip77\(([a-fA-F0-9]{64})\),elsh\((\w+)\(\[([a-fA-F0-9]{8})/([^\]]+)\](xpub[^/\)]+)(/[^\)]+)?\)\)\)(?:#[a-zA-Z0-9]+)?");
207-
if (!match.Success)
198+
var descriptor = NormalizeDescriptor(setupModel.LBTC.Descriptor);
199+
if (!TryParseLiquidDescriptor(descriptor, out var blindingKey, out var suffix, out var fingerprint,
200+
out var derivationPath, out var xpub, out var error))
208201
{
209-
result.Results[key] = new SamRockProtocolResponse(false,
210-
"Invalid LBTC descriptor format - could not parse slip77, script type, fingerprint, derivation path, and xpub.", null);
202+
result.Results[key] = new SamRockProtocolResponse(false, error, null);
211203
}
212204
else
213205
{
214-
var blindingKey = match.Groups[1].Value;
215-
var scriptType = match.Groups[2].Value;
216-
var fingerprint = match.Groups[3].Value;
217-
var basePath = match.Groups[4].Value;
218-
var xpub = match.Groups[5].Value;
219-
var addressSuffix = match.Groups[6].Value; // e.g., "/0/*"
220-
221-
// TODO: Check whether you need to combine base derivation path with address derivation suffix
222-
var derivationPath = basePath; // + (addressSuffix ?? "");
223-
224-
// Convert script type to NBXplorer suffix format
225-
//var suffix = GetNBXplorerSuffix(scriptType, descriptor);
226-
var suffix = "-[p2sh]"; // For LBTC at the moment of launch, we assume P2SH_P2WPKH
227-
if (suffix == null)
228-
{
229-
result.Results[key] = new SamRockProtocolResponse(false, $"Unsupported LBTC script type: {scriptType}", null);
230-
}
231-
else
232-
{
233-
// Create NBXplorer format derivation scheme for LBTC: xpub + suffix + slip77
234-
var derivationScheme = $"{xpub}{suffix}-[slip77={blindingKey}]";
235-
await SetupWalletAsync(derivationScheme, fingerprint, derivationPath, "LBTC", storeData, key, result);
236-
}
206+
var derivationScheme = $"{xpub}{suffix}-[slip77={blindingKey}]";
207+
await SetupWalletAsync(derivationScheme, fingerprint, derivationPath, "LBTC", storeData, key, result);
237208
}
238209
}
239210
catch (Exception lbtcex)
@@ -259,7 +230,7 @@ private async Task<IActionResult> processSamRockProtocolRequest(SamRockProtocolR
259230
}
260231
else
261232
{
262-
await boltzWrapper.SetBoltz(StoreId, setupModel.BTCLN.LBTC.Descriptor, result);
233+
await boltzWrapper.SetBoltz(StoreId, NormalizeDescriptor(setupModel.BTCLN.LBTC.Descriptor), result);
263234
}
264235
}
265236
else
@@ -291,6 +262,76 @@ private async Task<IActionResult> processSamRockProtocolRequest(SamRockProtocolR
291262
});
292263
}
293264

265+
private static string NormalizeDescriptor(string descriptor)
266+
{
267+
if (descriptor == null)
268+
return null;
269+
270+
return Regex.Replace(descriptor, @"\s+", string.Empty);
271+
}
272+
273+
private static string NormalizeDerivationPath(string path)
274+
{
275+
if (string.IsNullOrEmpty(path))
276+
return path;
277+
278+
return string.Join('/', path.Split('/').Select(component =>
279+
component.EndsWith("h", StringComparison.OrdinalIgnoreCase)
280+
? component[..^1] + "'"
281+
: component));
282+
}
283+
284+
private static bool TryParseLiquidDescriptor(string descriptor, out string blindingKey, out string suffix,
285+
out string fingerprint, out string derivationPath, out string xpub, out string error)
286+
{
287+
blindingKey = null;
288+
suffix = null;
289+
fingerprint = null;
290+
derivationPath = null;
291+
xpub = null;
292+
error = null;
293+
294+
if (string.IsNullOrWhiteSpace(descriptor))
295+
{
296+
error = "Invalid LBTC descriptor format - descriptor is empty.";
297+
return false;
298+
}
299+
300+
var nativeMatch = Regex.Match(descriptor,
301+
@"^ct\(slip77\(([a-fA-F0-9]{64})\),elwpkh\(\[([a-fA-F0-9]{8})/([^\]]+)\](xpub[^/\)]+)(/[^\)]+)?\)\)(?:#[a-zA-Z0-9]+)?$");
302+
if (nativeMatch.Success)
303+
{
304+
blindingKey = nativeMatch.Groups[1].Value;
305+
fingerprint = nativeMatch.Groups[2].Value;
306+
derivationPath = NormalizeDerivationPath(nativeMatch.Groups[3].Value);
307+
xpub = nativeMatch.Groups[4].Value;
308+
suffix = "";
309+
return true;
310+
}
311+
312+
var wrappedMatch = Regex.Match(descriptor,
313+
@"^ct\(slip77\(([a-fA-F0-9]{64})\),elsh\((\w+)\(\[([a-fA-F0-9]{8})/([^\]]+)\](xpub[^/\)]+)(/[^\)]+)?\)\)\)(?:#[a-zA-Z0-9]+)?$");
314+
if (wrappedMatch.Success)
315+
{
316+
var scriptType = wrappedMatch.Groups[2].Value;
317+
if (!string.Equals(scriptType, "wpkh", StringComparison.OrdinalIgnoreCase))
318+
{
319+
error = $"Unsupported LBTC script type: elsh({scriptType})";
320+
return false;
321+
}
322+
323+
blindingKey = wrappedMatch.Groups[1].Value;
324+
fingerprint = wrappedMatch.Groups[3].Value;
325+
derivationPath = NormalizeDerivationPath(wrappedMatch.Groups[4].Value);
326+
xpub = wrappedMatch.Groups[5].Value;
327+
suffix = "-[p2sh]";
328+
return true;
329+
}
330+
331+
error = "Invalid LBTC descriptor format - expected ct(slip77(...),elwpkh(...)) or ct(slip77(...),elsh(wpkh(...))).";
332+
return false;
333+
}
334+
294335
private async Task SetupWalletAsync(string derivationScheme, string fingerprint, string derivationPath, string networkCode,
295336
StoreData storeData, SamRockProtocolKeys key, SamRockProtocolSetupResponse result)
296337
{

0 commit comments

Comments
 (0)