Commit graph

19 commits

Author SHA1 Message Date
Håvard Kittelsen
763754b30e feat(core): Bitcoin Signed Message primitives, checked against Electron Cash
Dapps have been asking for message signing to prove key control — identity
verification, SIWX-style login, and publishing a signed statement on chain. This
adds the construction and the verification; the protocol wiring follows
separately.

The known workaround is a dummy transaction: one input with a null outpoint, its
prevout script set to the P2PKH of the key being proven, one OP_RETURN output
carrying a server nonce, signed but never broadcast. It covers login, but not the
rest. A signature made that way is bound to a transaction, so verifying it means
rebuilding that exact transaction and knowing how it was serialised — it cannot
be published in an OP_RETURN and checked later by a third party holding only the
message, the signature and an address. It also asks a wallet to sign a real
transaction preimage, which is one bug away from signing a genuine spend.

So this produces the portable form: the standard "Bitcoin Signed Message"
construction. BCH wallets kept Bitcoin's magic string verbatim, so a signature
made here verifies in Electron Cash, Electrum and `bitcoin-cli verifymessage`.
The magic prefix also guarantees the digest can never coincide with a transaction
sighash, which makes signing a message categorically safer to approve than
signing a dummy transaction.

Address-level API, because that is what verification actually looks like
elsewhere: Electron Cash exposes only `verify_message(address, sig, message)`,
and a third party pulling a proof off an explorer has an address, not a public
key. verifyMessageSignatureForAddress accepts CashAddr with or without a prefix
and legacy base58, and rejects P2SH — no message signature can prove control of a
script hash.

recoverMessageSigner returns { publicKey, compressed } rather than a bare key.
The header byte declares which serialisation was used, and a key's compressed and
uncompressed forms hash to DIFFERENT addresses. Dropping that bit is how a
signature proving control of one address gets accepted as proof of another;
message-signing.test.ts pins the case in both directions.

signBitcoinMessage takes the private key as an argument and never retains it. It
exists so a wallet calls one function instead of reassembling the magic string,
both compactSize prefixes and the header byte — the parts third-party verifiers
check, and the parts covered by the tests here.

Testing: the byte layout is not asserted against our own reimplementation of the
spec, because that catches a coding mistake but not a misreading of it.

  message-signing.vectors.json holds 32 vectors generated by a real Electron Cash
  4.4.5 install (contrib/generate-message-signing-vectors.py) — two keys, both
  compression forms, eight messages including empty, multi-byte UTF-8, multi-line
  and the 252/253-byte compactSize boundary. Every preimage hash must match byte
  for byte, and every signature must verify. Signature bytes are NOT portable
  across implementations — Electron Cash and libauth derive the ECDSA nonce
  differently — so the reproducible quantity is the hash.

  message-signing.compat.test.ts drives `electron-cash verifymessage` directly,
  closing the loop that vectors cannot: that our OUTPUT is accepted. Not part of
  `npm test` (each assertion spawns a full Electron Cash process); run
  `npm run test:compat -w @wizardconnect/core`. Skips when Electron Cash is
  absent, so CI is unaffected.

77 tests, 426 in core.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 14:28:41 +02:00
jakobsn
d580bb8927 Revert "Merge branch 'randomTradeSummary' into 'master'"
This reverts merge request !27
2026-05-11 08:46:49 +00:00
jakobsn
6b6f0ec1d0 TxSummary 2026-04-29 11:23:23 +00:00
jakobsn
443acccb80 Active reconnect. 2026-04-29 09:06:53 +00:00
Dagur Valberg Johannsson
b96cb8e151
Add chunk transport extension for oversized messages
Splits ProtocolMessages exceeding NIP-44's 65,535-byte plaintext ceiling
across multiple gift-wrapped events. Symmetric (both directions),
fire-and-forget, backward-compatible via a new transport-level
\`extensions\` field on \`dapp_ready\` and \`wallet_ready\`. Resolves the
\"Failed to swap: invalid plaintext size\" error on aggregated swap
requests and enables signed-tx responses up to the 1 MB BCH consensus
limit (~2 MB hex).
2026-04-21 14:58:12 +02:00
Dagur Valberg Johannsson
9028384177
Ensure messages are sent to all relays
To be compatible to wallets that only listen on 'relay.cauldron.quest',
we need to make sure we send our messages to all connected relay
servers.
2026-04-16 13:38:28 +02:00
Dagur Valberg Johannsson
c8e0a2d7cc
Tests for 22601be4 (tcp zombie) 2026-04-14 08:42:34 +02:00
Dagur Valberg Johannsson
ef56fb198c
Add additional default relay
For improved reliability, this adds an additional relay as a redundancy.
2026-04-14 08:42:34 +02:00
Dagur Valberg Johannsson
22601be42d
Detect stale tcp connections to relay
Enable nostr-tools' internal stale tcp connection detection
2026-04-08 09:47:50 +02:00
Dagur Valberg Johannsson
4d1ba8e207
Add 'extensions' to hdwalletv1 protocol
This backward compatible change allows wallets/dapps to add additional
features outside the basic transaction signature support to the
hdwalletv1 protocol.
2026-03-26 10:50:40 +01:00
Dagur Valberg Johannsson
fcfcd64d14
Replace nostr-dev-kit -> nostr-tools
The package nostr-dev-kit was using a dependency (tseep) which
downstream would flag as not CSP-safe.

Additionally nostr-tools footprint is much smaller.
2026-03-26 08:46:26 +01:00
Dagur Valberg Johannsson
889dd566d1
Add serialization helpers to @wizardconnect/core
Canonical encoding for BigInt (<bigint: Xn>) and Uint8Array (hex or
<Uint8Array: 0x...>) used in the relay protocol. Provides both
serialization (sourceOutputToRelay, transactionToHex) and deserialization
(parseExtendedJson, toUint8Array, toBigInt) so dapps and wallets
don't have to implement this independently.
2026-03-23 08:59:02 +01:00
Dagur Valberg Johannsson
cb4f1ee598
bug: Pass pubkey to initiateRelay on reconnect
Pass the pubkey if we already know it.
2026-03-23 08:34:51 +01:00
Dagur Valberg Johannsson
81a67825fe
Don't require input path for all inputs
Not all inputs need a signature; only require it for the input paths
that do.
2026-03-16 16:54:32 +01:00
Dagur Valberg Johannsson
8ef04218b4
Add input paths for a signature request
This solves an issue where wallet has to scan address ranges for each
derivation path and match it to locking script to figure out what
private key to use for signature.

This is a waste of effort and unnecessary complex for wallet
implementations since the dapp side already knows what inputs its using.
2026-03-16 15:27:18 +01:00
Dagur Valberg Johannsson
d3a1a36163
Add wallet pubkey to existing session
We need to also know the wallets pubkey if dapp is to connect to an
existing session.
2026-03-10 18:33:36 +01:00
Dagur Valberg Johannsson
0e54217929
Second attempt at trusted publishing 2026-03-09 17:05:45 +01:00
Dagur Valberg Johannsson
c564492473
Add CI/CD 2026-03-06 15:15:17 +01:00
Dagur Valberg Johannsson
6fce9b47cb
First commit for WizardConnect 2026-03-06 11:38:09 +01:00