@arkade-os/swap 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +125 -31
- package/dist/{chunk-DLM6BVTB.js → chunk-Q4FAYBXS.js} +6 -4
- package/dist/index.cjs +155 -93
- package/dist/index.d.cts +106 -25
- package/dist/index.d.ts +106 -25
- package/dist/index.js +67 -8
- package/dist/nostr.cjs +11 -0
- package/dist/nostr.d.cts +14 -2
- package/dist/nostr.d.ts +14 -2
- package/dist/nostr.js +9 -1
- package/dist/{rfq-DjZlesr4.d.cts → rfq-BH2yvo3O.d.cts} +36 -13
- package/dist/{rfq-DjZlesr4.d.ts → rfq-BH2yvo3O.d.ts} +36 -13
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -436,34 +436,42 @@ descriptor, which is public, and `contractSigner(wallet, descriptor)` recovers t
|
|
|
436
436
|
|
|
437
437
|
What each swap stores, and what is recoverable:
|
|
438
438
|
|
|
439
|
-
| Wallet answers with
|
|
440
|
-
|
|
|
441
|
-
| fresh HD descriptor
|
|
442
|
-
| static `tr(pubkey)`
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
439
|
+
| Wallet answers with | Spending key | Preimage (when the leg needs one) | Secret at rest |
|
|
440
|
+
| ------------------------- | --------------------- | ------------------------------------- | ----------------- |
|
|
441
|
+
| fresh HD descriptor | re-derives from seed | derives deterministically | none |
|
|
442
|
+
| static `tr(pubkey)` | the wallet's identity | derives from a public per-swap salt | none |
|
|
443
|
+
| a signer that cannot sign | | | |
|
|
444
|
+
| deterministically | the wallet's identity | random, stored on the record | the preimage only |
|
|
445
|
+
|
|
446
|
+
The preimage split follows the **descriptor's shape**, not the wallet's type. An HD child
|
|
447
|
+
descriptor is unique to its swap, so `sha256(sign_det(...))` over the key alone is safe. A static
|
|
448
|
+
descriptor is the same key for every swap, so that derivation would repeat across swaps — one
|
|
449
|
+
solver learning its own preimage would learn every other swap's — and the uniqueness has to come
|
|
450
|
+
from the message instead: the SDK mints 32 random bytes per swap, signs a **salted** message, and
|
|
451
|
+
stores the salt in the clear.
|
|
452
|
+
|
|
453
|
+
**The salt is not a secret.** Knowing it yields nothing without the seed, which is the whole
|
|
454
|
+
difference from the preimage it replaces: the record goes from carrying a per-swap _secret_ to a
|
|
455
|
+
per-swap _public_ value, exactly what `signingDescriptor` already is. Recoverability is unchanged
|
|
456
|
+
in shape — keep the record and the swap recovers from the seed.
|
|
457
|
+
|
|
458
|
+
Only a signer that cannot sign deterministically at all — an external or extension signer — still
|
|
459
|
+
gets a random stored preimage. `mustPersistPreimage` says which you got, and it is the only thing
|
|
460
|
+
to branch on. A stored preimage remains the one secret at rest in the design, and it is never a
|
|
461
|
+
private key.
|
|
450
462
|
|
|
451
463
|
```ts
|
|
452
464
|
const swap = await requestOnchainSend(/* … */);
|
|
453
|
-
// `swapSecretsToRecord` stores the public descriptor always,
|
|
454
|
-
//
|
|
465
|
+
// `swapSecretsToRecord` stores the public descriptor always, then whichever of
|
|
466
|
+
// `preimageSaltHex` (derivable) or `preimageHex` (not) the wallet produced.
|
|
455
467
|
await saveSwap({ ...record, ...swapSecretsToRecord(swap.secrets) });
|
|
456
468
|
|
|
457
|
-
// Later, from the seed plus
|
|
458
|
-
// corridor gave us one for: a lightning send's P belongs to the
|
|
459
|
-
// this throws on those records rather than inventing something the
|
|
460
|
-
// never match. `LIGHTNING_SEND_PAIR` is exported from this package.
|
|
461
|
-
if (record.
|
|
462
|
-
const preimage = await
|
|
463
|
-
wallet,
|
|
464
|
-
record.signingDescriptor,
|
|
465
|
-
record.preimageHex ? hex.decode(record.preimageHex) : undefined,
|
|
466
|
-
);
|
|
469
|
+
// Later, from the seed plus the record's public fields. Only ask for a
|
|
470
|
+
// preimage the corridor gave us one for: a lightning send's P belongs to the
|
|
471
|
+
// payee, so this throws on those records rather than inventing something the
|
|
472
|
+
// chain will never match. `LIGHTNING_SEND_PAIR` is exported from this package.
|
|
473
|
+
if (record.pair !== LIGHTNING_SEND_PAIR) {
|
|
474
|
+
const preimage = await preimageForSwapRecord(wallet, record);
|
|
467
475
|
}
|
|
468
476
|
|
|
469
477
|
// For a refund, take the composition instead of the guard: it turns all three
|
|
@@ -481,21 +489,45 @@ terminal: the lockup stays funded and watched, a solver claim still ends the swa
|
|
|
481
489
|
`pending`. The manager reports the same state when nothing is wired to act (`enableAutoActions:
|
|
482
490
|
false`, or no callbacks) and the window has passed.
|
|
483
491
|
|
|
492
|
+
`preimageForSwapRecord` is the read path to wire, not a hand-rolled `contractPreimage` call: it
|
|
493
|
+
knows which of the record's fields are derivation inputs, and it verifies the result against
|
|
494
|
+
`paymentHash`. A caller that forgets to pass the salt gets a _wrong_ preimage from a wallet that can
|
|
495
|
+
derive, not an error — and that surfaces as an opaque script failure at claim time.
|
|
496
|
+
|
|
497
|
+
Every refusal is a `PreimageNotRecoverableError` carrying a `reason`: `no-secrets` (the record
|
|
498
|
+
predates the descriptor), `malformed-record`, `not-derivable` (nothing to derive from, or a key this
|
|
499
|
+
wallet does not hold), or `hash-mismatch` (derived, but wrong — a tampered salt or the wrong seed).
|
|
500
|
+
Branch on `reason`, never on message text. It is deliberately **not**
|
|
501
|
+
`RefundNotLocallyPossibleError`: that one means no local refund is possible and `RfqSwapManager`
|
|
502
|
+
reports `needs_counterparty` for it, which is a different verdict from a claim-path read failing.
|
|
503
|
+
|
|
484
504
|
A caller-supplied preimage keeps `signingDescriptor` for the sender key and stores only
|
|
485
505
|
`preimageHex` as secret material.
|
|
486
506
|
|
|
487
507
|
On an HD wallet each swap **allocates** its own descriptor rather than peeking at the current one:
|
|
488
508
|
two swaps sharing a descriptor derive the _identical_ preimage, so one solver learning its own
|
|
489
|
-
preimage would learn the other swap's. (Static wallets share their one descriptor by design —
|
|
490
|
-
is
|
|
509
|
+
preimage would learn the other swap's. (Static wallets share their one descriptor by design — the
|
|
510
|
+
per-swap salt is what separates their preimages instead.) On restore, `adoptContractDescriptor`
|
|
491
511
|
(from `@arkade-os/sdk`) moves the wallet's watermark past a restored record's index so it cannot be
|
|
492
512
|
handed out twice; a static descriptor names no index and adopts as a no-op.
|
|
493
513
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
514
|
+
Two derivations, picked by the descriptor's shape:
|
|
515
|
+
|
|
516
|
+
```
|
|
517
|
+
HD child sha256(sign_det(sha256("Arkade-RFQ-Preimage-v1" ‖ xonly(32) ‖ u32le(0))))
|
|
518
|
+
static/salted sha256(sign_det(sha256("Arkade-Contract-Preimage-Salted-v1" ‖ xonly(32) ‖ salt(32))))
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
The first mirrors NArk's Boltz scheme (`SwapsManagementService.cs:128-160`) with an RFQ-scoped tag.
|
|
522
|
+
NArk has no RFQ corridor yet, so this tag defines the scheme rather than matching one; it is
|
|
523
|
+
deliberately distinct from the Boltz tag so one wallet key cannot derive the same preimage for both
|
|
524
|
+
corridors.
|
|
525
|
+
|
|
526
|
+
The salted tag is corridor-generic where the first is not, and that asymmetry is deliberate: the v1
|
|
527
|
+
tags must be per-corridor because v1 pins its message index, leaving the tag as the only separation
|
|
528
|
+
between two corridors reaching the same key. The salted form mints a fresh salt per swap, so no two
|
|
529
|
+
swaps share a message within a corridor or across two — the salt carries the separation, and the tag
|
|
530
|
+
names the layer rather than the corridor.
|
|
499
531
|
|
|
500
532
|
**Not covered:** seed-only discovery after the swap repository is wiped. An unspent L1 HTLC reveals
|
|
501
533
|
too little public quote data to rediscover, so the record remains required.
|
|
@@ -508,9 +540,71 @@ before later-funded addresses are found. Keep the swap repository in backups (re
|
|
|
508
540
|
each record's descriptor via `adoptContractDescriptor`), or raise `gapLimit` on seed-only restores
|
|
509
541
|
after heavy swap use.
|
|
510
542
|
|
|
543
|
+
## Upgrading from 0.0.3
|
|
544
|
+
|
|
545
|
+
0.0.1–0.0.3 are published. Under npm's 0.0.x rules `^0.0.3` resolves to exactly 0.0.3, so nothing
|
|
546
|
+
auto-upgrades into the changes below — but a consumer that does upgrade meets them all in one jump,
|
|
547
|
+
so they are written as one migration rather than per-release fragments.
|
|
548
|
+
|
|
549
|
+
**Key provisioning moved into the SDK.** `packages/swap/src/secrets.ts` is gone. `deriveSwapSecrets`,
|
|
550
|
+
`randomSwapSecrets`, `preimageForRfqSecrets`, `senderIdentityForRfqSecrets`, `rfqSecretsToRecord`,
|
|
551
|
+
`rfqSecretsOfRecord`, `isPerSwapDescriptor`, `RFQ_PREIMAGE_TAG` and `SwapSecrets` no longer exist.
|
|
552
|
+
Import `provisionRefundKey`, `provisionClaimSecret`, `contractSigner`, `contractPreimage`,
|
|
553
|
+
`isPerArtifactDescriptor` and `ARKADE_SWAP_PREIMAGE_TAG` from `@arkade-os/sdk` instead;
|
|
554
|
+
`swapSecretsToRecord` and `senderIdentityForSwapRecord` stay in this package. No consumer branches
|
|
555
|
+
on wallet type any more, and no swap record can carry a private key.
|
|
556
|
+
|
|
557
|
+
**`contractPreimage` takes an options object.** `contractPreimage(wallet, descriptor, stored?)`
|
|
558
|
+
became `contractPreimage(wallet, descriptor, { stored?, salt? })`. Prefer `preimageForSwapRecord`,
|
|
559
|
+
which reads both fields off the record and verifies against `paymentHash`.
|
|
560
|
+
|
|
561
|
+
**Static wallets derive their preimage instead of storing it.** New records from such wallets carry
|
|
562
|
+
`preimageSaltHex` and no `preimageHex`; `mustPersistPreimage` is now `false` for them, so the
|
|
563
|
+
"persist the preimage" warning stops firing. Nothing at rest is secret unless the signer cannot sign
|
|
564
|
+
deterministically at all.
|
|
565
|
+
|
|
566
|
+
**`AssetSwap` gains `preimageSaltHex?`, and `AssetSwapRepository.version` is `2`.** External
|
|
567
|
+
repository implementations must recompile — deliberately, because a field-mapped backend that drops
|
|
568
|
+
`preimageSaltHex` leaves the swap unclaimable exactly as one dropping `preimageHex` does. Records
|
|
569
|
+
written by 0.0.1–0.0.3 need no rewrite and no migration: the field is optional, older rows resolve
|
|
570
|
+
through their stored `preimageHex` or their HD descriptor, and `DB_VERSION` is unchanged.
|
|
571
|
+
|
|
511
572
|
## Breaking changes on this branch (pre-release migration notes)
|
|
512
573
|
|
|
513
|
-
|
|
574
|
+
Notes from before 0.0.1, kept for consumers who tracked the branch.
|
|
575
|
+
|
|
576
|
+
- **Every derived address changed again, in both corridors — the unilateral ladder was re-spaced.**
|
|
577
|
+
`unilateralRefundDelay` now sits **level with** `claimDelay` instead of one 512s step above it,
|
|
578
|
+
and `unilateralRefundWithoutReceiverDelay` sits `SOLO_REFUND_HEADROOM_SECONDS` (4096s, newly
|
|
579
|
+
exported) above it instead of two steps. The old ladder spaced all three leaves one step apart as
|
|
580
|
+
though they were interchangeable rungs; they are not. Only `unilateralRefundWithoutReceiver` is a
|
|
581
|
+
solo path for the funder, so it is the only one whose timing can steal, and one 512s tick was
|
|
582
|
+
never enough for a claimant to complete a unilateral exit in. The two-signature refund needs no
|
|
583
|
+
separation at all, since neither party can spend that leaf alone. This tracks the reference
|
|
584
|
+
solver's [lightning-swap-service#81](https://github.com/arkade-os/lightning-swap-service/pull/81);
|
|
585
|
+
the two derivations must produce **the same three delay values** for the same operator, which is
|
|
586
|
+
what keeps the derived addresses identical. **Deployment must be coordinated** on the same terms
|
|
587
|
+
as the entry below: for a quote not yet funded, a mismatch refuses it at `verifyLockupAddress`
|
|
588
|
+
rather than losing funds.
|
|
589
|
+
|
|
590
|
+
**An in-flight lockup funded before the upgrade needs care, and the entry below understates
|
|
591
|
+
this.** The delays are not quote fields and are not persisted on the swap record
|
|
592
|
+
(`AssetSwap` keeps `swapPkScript`, not `claimDelay`), and `RfqSwap`'s own doc tells callers to
|
|
593
|
+
*rebuild* the script on restart from the quote's binding fields — which re-derives the delays
|
|
594
|
+
under whatever ladder is compiled in. So a trader who funded on `0.0.4`, upgraded, and restarted
|
|
595
|
+
rebuilds a **new** address, and `refundIfUnresolved` finds no VTXOs there and returns
|
|
596
|
+
`nothing_to_refund` — a terminal-sounding answer for money still locked at the old script, with
|
|
597
|
+
`refundLocktime` still ticking. Until the delays are persisted and rebuilt from the stored value,
|
|
598
|
+
drain in-flight lockups before upgrading, or rebuild the old script from the pre-upgrade delays
|
|
599
|
+
by hand. This is a pre-existing gap that any address-moving change hits, not one this change
|
|
600
|
+
introduces.
|
|
601
|
+
|
|
602
|
+
`unilateralClaimDelay`'s BIP68 ceiling tightened to reserve the full headroom rather than two
|
|
603
|
+
steps. Note this guard alone is **not** mirrored in the reference solver, which still rejects
|
|
604
|
+
only above `0xffff * 512`: for an operator `unilateralExitDelay` in `(33549824, 33553920]`
|
|
605
|
+
seconds the trader throws here while the solver quotes and then fails deeper in its own script
|
|
606
|
+
build. Both refuse, at different seams with different messages, so it is a diagnosability wart
|
|
607
|
+
rather than a fund risk — and the window is unreachable in practice (~388 days).
|
|
514
608
|
|
|
515
609
|
- **`secrets.ts` is gone; key provisioning moved into `@arkade-os/sdk`.** This package no longer
|
|
516
610
|
derives, mints, or names keys. It asks the SDK for what the leg needs — `provisionRefundKey(wallet)`
|
|
@@ -546,21 +546,22 @@ var relayTransport = (relayUrl, options) => {
|
|
|
546
546
|
};
|
|
547
547
|
};
|
|
548
548
|
var SEQUENCE_GRANULARITY_SECONDS = 512;
|
|
549
|
+
var SOLO_REFUND_HEADROOM_SECONDS = 8 * SEQUENCE_GRANULARITY_SECONDS;
|
|
549
550
|
var unilateralClaimDelay = (serverExitDelaySeconds) => {
|
|
550
551
|
if (!Number.isFinite(serverExitDelaySeconds) || serverExitDelaySeconds < SEQUENCE_GRANULARITY_SECONDS) {
|
|
551
552
|
throw new Error(
|
|
552
553
|
`server exit delay must be at least ${SEQUENCE_GRANULARITY_SECONDS}s of seconds, got ${serverExitDelaySeconds}`
|
|
553
554
|
);
|
|
554
555
|
}
|
|
555
|
-
if (serverExitDelaySeconds >
|
|
556
|
+
if (serverExitDelaySeconds > 65535 * SEQUENCE_GRANULARITY_SECONDS - SOLO_REFUND_HEADROOM_SECONDS) {
|
|
556
557
|
throw new Error(
|
|
557
|
-
`server exit delay ${serverExitDelaySeconds}s exceeds what BIP68 can encode once the
|
|
558
|
+
`server exit delay ${serverExitDelaySeconds}s exceeds what BIP68 can encode once the solo refund's headroom is stacked above it`
|
|
558
559
|
);
|
|
559
560
|
}
|
|
560
561
|
return Math.ceil(serverExitDelaySeconds / SEQUENCE_GRANULARITY_SECONDS) * SEQUENCE_GRANULARITY_SECONDS;
|
|
561
562
|
};
|
|
562
|
-
var unilateralRefundDelay = (claimDelay) => claimDelay
|
|
563
|
-
var unilateralRefundWithoutReceiverDelay = (claimDelay) => claimDelay +
|
|
563
|
+
var unilateralRefundDelay = (claimDelay) => claimDelay;
|
|
564
|
+
var unilateralRefundWithoutReceiverDelay = (claimDelay) => claimDelay + SOLO_REFUND_HEADROOM_SECONDS;
|
|
564
565
|
function lightningSendVtxoScript(params) {
|
|
565
566
|
const seconds = (value) => ({
|
|
566
567
|
type: "seconds",
|
|
@@ -1169,6 +1170,7 @@ export {
|
|
|
1169
1170
|
assertFundable,
|
|
1170
1171
|
httpTransport,
|
|
1171
1172
|
relayTransport,
|
|
1173
|
+
SOLO_REFUND_HEADROOM_SECONDS,
|
|
1172
1174
|
unilateralClaimDelay,
|
|
1173
1175
|
unilateralRefundDelay,
|
|
1174
1176
|
unilateralRefundWithoutReceiverDelay,
|