import 'dart:convert'; import 'package:bip340/bip340.dart' as bip340; import 'package:swarm/swarm.dart'; // Low-level primitives (not part of the public surface) needed to hand-craft a // spoofed gift wrap for the anti-spoofing test. import 'package:swarm/src/nip44.dart'; import 'package:swarm/src/nostr_event.dart'; import 'package:test/test.dart'; const _senderPriv = '0000000000000000000000000000000000000000000000000000000000000001'; const _recipientPriv = '0000000000000000000000000000000000000000000000000000000000000002'; void main() { final senderPub = bip340.getPublicKey(_senderPriv); final recipientPub = bip340.getPublicKey(_recipientPriv); test('WrapDirectMessage produces a kind-1059 gift wrap', () async { final actor = NostrDmActor(_senderPriv); final result = await actor.handle( WrapDirectMessage(recipientPubKey: recipientPub, content: 'hi'), ); expect(result, isA()); final event = jsonDecode((result as GiftWrapped).event) as Map; expect(event['kind'], kGiftWrapKind); // The wrap is signed by an ephemeral key, never the sender. expect(event['pubkey'], isNot(equals(senderPub))); final pTags = (event['tags'] as List) .cast() .where((t) => t.isNotEmpty && t[0] == 'p'); expect(pTags.single[1], equals(recipientPub)); }); test('recipient unwraps the gift back to the original rumor', () async { final actor = NostrDmActor(_senderPriv); final result = await actor.handle( WrapDirectMessage( recipientPubKey: recipientPub, content: 'check out this image', rumorTags: const [ ['imeta', 'url https://cdn.example/x.png', 'm image/png'], ], ), ) as GiftWrapped; final rumor = unwrapGift(result.event, _recipientPriv); expect(rumor['content'], equals('check out this image')); expect(rumor['kind'], equals(kDirectMessageKind)); expect(rumor['pubkey'], equals(senderPub)); final tags = (rumor['tags'] as List).cast(); expect( tags.any((t) => t.isNotEmpty && t[0] == 'p' && t[1] == recipientPub), isTrue, ); expect( tags.any((t) => t.isNotEmpty && t[0] == 'imeta'), isTrue, ); }); test('a different key cannot unwrap the gift', () async { final actor = NostrDmActor(_senderPriv); final result = await actor.handle( WrapDirectMessage(recipientPubKey: recipientPub, content: 'secret'), ) as GiftWrapped; const otherPriv = '0000000000000000000000000000000000000000000000000000000000000003'; expect(() => unwrapGift(result.event, otherPriv), throwsA(anything)); }); test('UnwrapDirectMessage authenticates sender and returns contents', () async { final sender = NostrDmActor(_senderPriv); final wrapped = await sender.handle( WrapDirectMessage( recipientPubKey: recipientPub, content: 'see attached', rumorTags: const [ ['imeta', 'url https://cdn.example/x.png', 'm image/png'], ], ), ) as GiftWrapped; final recipient = NostrDmActor(_recipientPriv); final unwrapped = await recipient.handle( UnwrapDirectMessage(wrapped.event), ); expect(unwrapped, isA()); final dm = unwrapped as DirectMessageUnwrapped; expect(dm.senderPubKey, equals(senderPub)); expect(dm.content, equals('see attached')); expect( dm.tags.any((t) => t.isNotEmpty && t[0] == 'imeta'), isTrue, ); }); test('a spoofed rumor author is rejected', () async { // Hand-build a gift wrap whose inner rumor claims a different author than // the seal signer — the seal is honestly signed but the rumor lies. const attackerPriv = '0000000000000000000000000000000000000000000000000000000000000009'; final attackerPub = bip340.getPublicKey(attackerPriv); final rumor = { 'id': '0' * 64, 'pubkey': senderPub, // claims to be the trusted sender 'created_at': 1700000000, 'kind': kDirectMessageKind, 'tags': [ ['p', recipientPub], ], 'content': 'totally legit', }; final sealContent = nip44Encrypt(jsonEncode(rumor), attackerPriv, recipientPub); final seal = jsonDecode( signNostrEvent( privateKey: attackerPriv, publicKey: attackerPub, kind: kSealKind, tags: const [], content: sealContent, ), ) as Map; final wrap = nip44Encrypt(jsonEncode(seal), attackerPriv, recipientPub); final giftWrapEvent = signNostrEvent( privateKey: attackerPriv, publicKey: attackerPub, kind: kGiftWrapKind, tags: [ ['p', recipientPub], ], content: wrap, ); expect( () => unwrapGift(giftWrapEvent, _recipientPriv), throwsA(anything), ); }); }