Kontext
Mit DFXswiss/packages#165 existiert das npm-Package @dfx.swiss/bip322-multisig, das BIP-322 PSBTs für P2WSH Multisig-Wallets baut und Signaturen extrahiert. Das Backend (api#3637) kann BIP-322 P2WSH Signaturen bereits verifizieren.
Dieses Issue beschreibt die Integration in das services-Frontend, damit User mit Sparrow (oder anderem PSBT-fähigen Tool) ihre Multisig-Wallet authentifizieren können.
Anforderung
User mit einer P2WSH Multisig-Wallet (bc1q..., 62 Zeichen) sollen sich über die services-Oberfläche authentifizieren können. Der Flow:
- User gibt seinen
wsh(sortedmulti(...)) Descriptor und seine Adresse ein
- Frontend holt Challenge-Message vom Backend (
getSignMessage)
- Frontend baut den BIP-322 PSBT (via Package)
- User lädt PSBT herunter → signiert in Sparrow mit M-of-N Cosignern
- User lädt signierten PSBT hoch (File-Upload oder Base64-Paste)
- Frontend extrahiert die BIP-322 Simple Signature (via Package)
- Normale Auth mit
{ address, signature } → login()
Technische Umsetzung
1. Dependency installieren
npm install @dfx.swiss/bip322-multisig
Benötigte Imports:
import { parseDescriptor, buildBip322Psbt, extractBip322Signature } from '@dfx.swiss/bip322-multisig';
2. Neuer WalletType
Datei: src/contexts/wallet.context.tsx
Drei Einträge hinzufügen (nach dem Muster der bestehenden Wallet-Typen):
// Im enum WalletType:
MULTISIG_BTC = 'MultiSigBtc',
// In WalletBlockchains:
[WalletType.MULTISIG_BTC]: [Blockchain.BITCOIN],
// In WalletTypeMap:
[WalletType.MULTISIG_BTC]: AuthWalletType.CLI,
AuthWalletType.CLI weil das Backend keinen separaten Multisig-Typ braucht — es erkennt BIP-322 P2WSH automatisch als 4. Versuch in crypto.service.ts:verifyBitcoinBased.
3. Neuer Connector: connect-multisig.tsx
Datei: src/components/home/wallet/connect-multisig.tsx
Folgt dem CLI-Pattern (connect-cli.tsx): ConnectBase + Form + getAccount() liefert { address, signature }. Kein separater Hook nötig.
Form-Interface:
interface FormData {
address: string;
descriptor: string;
signature: string;
}
Interner State-Flow (im Content-Component):
[Step 1: Input]
- Descriptor: Textarea (mehrzeilig, Sparrow exportiert oft mit Zeilenumbrüchen)
→ Validierung: parseDescriptor() aus dem Package (wirft bei ungültigem Format)
- Address: Input, Regex /^bc1q[a-z0-9]{58}$/ (nur native P2WSH)
→ Validierung: findAddress() aus dem Package prüft, ob Adresse zum Descriptor gehört
[Step 2: PSBT Download]
- Wenn Descriptor + Address valide:
→ getSignMessage(address) vom Backend holen
→ buildBip322Psbt({ message, descriptor, address }) aufrufen
→ PSBT Base64 als Datei zum Download anbieten (Filename: bip322-tosign.psbt)
→ Anleitung anzeigen: "Öffne die PSBT-Datei in Sparrow, signiere mit den erforderlichen Cosignern, speichere die signierte PSBT"
[Step 3: Upload + Extract]
- File-Upload (.psbt) ODER Base64-Textarea (wie loadPsbtBase64 in bin/finalize.ts)
- Bei Upload: extractBip322Signature(psbtBase64) aufrufen
- Extrahierte Signatur ins Form-Feld `signature` setzen (hidden)
- Submit-Button aktivieren
[Step 4: Submit]
- getAccount() liefert { address, signature }
- ConnectBase ruft login(wallet, address, blockchain, () => Promise.resolve(signature))
- Normaler Auth-Flow, kein Unterschied zum CLI-Login
ConnectBase-Integration:
export default function ConnectMultisig(props: ConnectProps): JSX.Element {
const { session } = useAuthContext();
const { activeWallet } = useWalletContext();
const form = useForm<FormData>({ mode: 'onTouched' });
async function getAccount(_w: WalletType, _b: Blockchain, isReconnect: boolean): Promise<Account> {
if (isReconnect && session?.address) return { address: session.address };
const values = form.getValues();
return values; // { address, signature }
}
async function getSignature(): Promise<never> {
throw new Error('Invalid signature call'); // Wird nie aufgerufen — Signatur ist pre-provided
}
return (
<ConnectBase
isSupported={() => true}
getAccount={getAccount}
signMessage={getSignature}
renderContent={(p) => <Content form={form} {...p} />}
autoConnect={activeWallet === props.wallet}
{...props}
/>
);
}
UI-Elemente (im Content-Component):
- Descriptor-Input:
<StyledTextArea> mit Validierung via parseDescriptor()
- Address-Input:
<StyledInput> mit Regex-Validierung
- PSBT-Download-Button: Erzeugt Blob →
URL.createObjectURL → Download-Link
- PSBT-Upload:
<input type="file" accept=".psbt"> + Base64-Konvertierung
- Status-Anzeige: Zeigt an, ob Signatur erfolgreich extrahiert wurde
- Submit-Button: Standard
connect() aus ConnectContentProps
Error Handling:
parseDescriptor() wirft bei ungültigem Descriptor → Fehlermeldung beim Descriptor-Feld
buildBip322Psbt() wirft wenn Adresse nicht ableitbar → Fehlermeldung beim Address-Feld
extractBip322Signature() wirft wenn PSBT nicht finalisiert → "PSBT ist nicht vollständig signiert"
4. Routing registrieren
Datei: src/components/home/connect-wrapper.tsx
// Import hinzufügen:
import ConnectMultisig from './wallet/connect-multisig';
// Im switch-Statement:
case WalletType.MULTISIG_BTC:
return <ConnectMultisig {...props} />;
5. Feature-Tree
Datei: src/config/feature-tree.ts
Neues Tile in die Wallet-Auswahl einfügen (z.B. neben dem CLI-Tile):
{
id: 'multisig',
img: 'multisig', // Icon muss erstellt werden
wallet: { type: WalletType.MULTISIG_BTC, blockchain: Blockchain.BITCOIN },
},
Hinweis: Ein passendes Icon (z.B. Vault/Tresor-Symbol) muss als Asset hinzugefügt werden.
6. Translations
In den Sprachdateien (translations/de.json, en.json, fr.json, it.json) Texte hinzufügen:
{
"multisig_descriptor": "Multisig Descriptor",
"multisig_descriptor_hint": "wsh(sortedmulti(...)) aus Sparrow/Coldcard exportieren",
"multisig_download_psbt": "PSBT herunterladen",
"multisig_upload_psbt": "Signierte PSBT hochladen",
"multisig_signature_extracted": "Signatur extrahiert",
"multisig_psbt_not_finalized": "PSBT ist nicht vollständig signiert — alle erforderlichen Cosigner müssen unterschreiben"
}
Referenzimplementation
- CLI-Flow (identischer Ablauf, anderes Interface):
packages/bip322-multisig/src/bin/sign.ts + finalize.ts
- CLI-Connector-Pattern:
src/components/home/wallet/connect-cli.tsx — das Muster, dem der neue Connector folgt
- Package Public API:
parseDescriptor(), buildBip322Psbt(), extractBip322Signature()
Geschätzter Umfang
| Datei |
Änderung |
Zeilen ca. |
package.json |
+1 Dependency |
1 |
wallet.context.tsx |
+3 Einträge |
5 |
connect-wrapper.tsx |
+1 Case |
3 |
feature-tree.ts |
+1 Tile |
5 |
connect-multisig.tsx |
Neuer Component |
~200 |
| Translation-Files |
+6 Keys × 4 Sprachen |
24 |
| Total |
|
~240 |
Abhängigkeiten
Akzeptanzkriterien
Kontext
Mit DFXswiss/packages#165 existiert das npm-Package
@dfx.swiss/bip322-multisig, das BIP-322 PSBTs für P2WSH Multisig-Wallets baut und Signaturen extrahiert. Das Backend (api#3637) kann BIP-322 P2WSH Signaturen bereits verifizieren.Dieses Issue beschreibt die Integration in das services-Frontend, damit User mit Sparrow (oder anderem PSBT-fähigen Tool) ihre Multisig-Wallet authentifizieren können.
Anforderung
User mit einer P2WSH Multisig-Wallet (
bc1q..., 62 Zeichen) sollen sich über die services-Oberfläche authentifizieren können. Der Flow:wsh(sortedmulti(...))Descriptor und seine Adresse eingetSignMessage){ address, signature }→login()Technische Umsetzung
1. Dependency installieren
Benötigte Imports:
2. Neuer WalletType
Datei:
src/contexts/wallet.context.tsxDrei Einträge hinzufügen (nach dem Muster der bestehenden Wallet-Typen):
AuthWalletType.CLIweil das Backend keinen separaten Multisig-Typ braucht — es erkennt BIP-322 P2WSH automatisch als 4. Versuch incrypto.service.ts:verifyBitcoinBased.3. Neuer Connector:
connect-multisig.tsxDatei:
src/components/home/wallet/connect-multisig.tsxFolgt dem CLI-Pattern (
connect-cli.tsx):ConnectBase+ Form +getAccount()liefert{ address, signature }. Kein separater Hook nötig.Form-Interface:
Interner State-Flow (im Content-Component):
ConnectBase-Integration:
UI-Elemente (im Content-Component):
<StyledTextArea>mit Validierung viaparseDescriptor()<StyledInput>mit Regex-ValidierungURL.createObjectURL→ Download-Link<input type="file" accept=".psbt">+ Base64-Konvertierungconnect()aus ConnectContentPropsError Handling:
parseDescriptor()wirft bei ungültigem Descriptor → Fehlermeldung beim Descriptor-FeldbuildBip322Psbt()wirft wenn Adresse nicht ableitbar → Fehlermeldung beim Address-FeldextractBip322Signature()wirft wenn PSBT nicht finalisiert → "PSBT ist nicht vollständig signiert"4. Routing registrieren
Datei:
src/components/home/connect-wrapper.tsx5. Feature-Tree
Datei:
src/config/feature-tree.tsNeues Tile in die Wallet-Auswahl einfügen (z.B. neben dem CLI-Tile):
Hinweis: Ein passendes Icon (z.B. Vault/Tresor-Symbol) muss als Asset hinzugefügt werden.
6. Translations
In den Sprachdateien (
translations/de.json,en.json,fr.json,it.json) Texte hinzufügen:{ "multisig_descriptor": "Multisig Descriptor", "multisig_descriptor_hint": "wsh(sortedmulti(...)) aus Sparrow/Coldcard exportieren", "multisig_download_psbt": "PSBT herunterladen", "multisig_upload_psbt": "Signierte PSBT hochladen", "multisig_signature_extracted": "Signatur extrahiert", "multisig_psbt_not_finalized": "PSBT ist nicht vollständig signiert — alle erforderlichen Cosigner müssen unterschreiben" }Referenzimplementation
packages/bip322-multisig/src/bin/sign.ts+finalize.tssrc/components/home/wallet/connect-cli.tsx— das Muster, dem der neue Connector folgtparseDescriptor(),buildBip322Psbt(),extractBip322Signature()Geschätzter Umfang
package.jsonwallet.context.tsxconnect-wrapper.tsxfeature-tree.tsconnect-multisig.tsxAbhängigkeiten
@dfx.swiss/bip322-multisigPackage muss auf npm publiziert sein (feat(bip322-multisig): new package for BIP-322 PSBTs from P2WSH descriptors packages#165)Akzeptanzkriterien