@decentnetwork/peer 0.1.46 → 0.1.48
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/dist/crypto/sign.d.ts +12 -1
- package/dist/crypto/sign.js +43 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/peer.d.ts +8 -0
- package/dist/peer.js +36 -5
- package/package.json +3 -2
package/dist/crypto/sign.d.ts
CHANGED
|
@@ -1 +1,12 @@
|
|
|
1
|
-
export declare
|
|
1
|
+
export declare const SIGNATURE_LENGTH = 64;
|
|
2
|
+
/**
|
|
3
|
+
* Produce a detached XEdDSA signature over `message` using a 32-byte X25519
|
|
4
|
+
* secret key (a Carrier identity secret key). Returns a 64-byte signature.
|
|
5
|
+
*/
|
|
6
|
+
export declare function signDetached(secretKey: Uint8Array, message: Uint8Array): Uint8Array;
|
|
7
|
+
/**
|
|
8
|
+
* Verify a detached XEdDSA signature against a 32-byte X25519 public key
|
|
9
|
+
* (decode the userid to get it). Returns false on any malformed input rather
|
|
10
|
+
* than throwing, so callers can treat it as a plain predicate.
|
|
11
|
+
*/
|
|
12
|
+
export declare function verifyDetached(publicKey: Uint8Array, message: Uint8Array, signature: Uint8Array): boolean;
|
package/dist/crypto/sign.js
CHANGED
|
@@ -1,3 +1,44 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Detached signatures over a Carrier identity's key.
|
|
2
|
+
//
|
|
3
|
+
// A Carrier/toxcore identity key is an X25519 (Curve25519) key pair — the
|
|
4
|
+
// same key used for crypto_box, and the public half IS the userid (base58).
|
|
5
|
+
// X25519 keys can't be fed to Ed25519 (nacl.sign) directly, so we use XEdDSA
|
|
6
|
+
// (Signal's scheme): sign with the Montgomery/X25519 private key, and verify
|
|
7
|
+
// against the X25519 public key. A relying party (e.g. a website doing
|
|
8
|
+
// "Sign in with Decent") decodes the userid → 32-byte X25519 public key and
|
|
9
|
+
// verifies with the same primitive — no separate signing key, no new key
|
|
10
|
+
// derivation path, the signature is bound to the identity itself.
|
|
11
|
+
//
|
|
12
|
+
// curve25519-js implements XEdDSA sign/verify; randomization (64 bytes) makes
|
|
13
|
+
// signatures non-deterministic per the spec. Verification is independent of
|
|
14
|
+
// the random used.
|
|
15
|
+
import { sign as xeddsaSign, verify as xeddsaVerify } from "curve25519-js";
|
|
16
|
+
import nacl from "tweetnacl";
|
|
17
|
+
export const SIGNATURE_LENGTH = 64;
|
|
18
|
+
/**
|
|
19
|
+
* Produce a detached XEdDSA signature over `message` using a 32-byte X25519
|
|
20
|
+
* secret key (a Carrier identity secret key). Returns a 64-byte signature.
|
|
21
|
+
*/
|
|
22
|
+
export function signDetached(secretKey, message) {
|
|
23
|
+
if (secretKey.length !== nacl.box.secretKeyLength) {
|
|
24
|
+
throw new Error(`secretKey must be ${nacl.box.secretKeyLength} bytes`);
|
|
25
|
+
}
|
|
26
|
+
const random = nacl.randomBytes(64);
|
|
27
|
+
return xeddsaSign(secretKey, message, random);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Verify a detached XEdDSA signature against a 32-byte X25519 public key
|
|
31
|
+
* (decode the userid to get it). Returns false on any malformed input rather
|
|
32
|
+
* than throwing, so callers can treat it as a plain predicate.
|
|
33
|
+
*/
|
|
34
|
+
export function verifyDetached(publicKey, message, signature) {
|
|
35
|
+
if (publicKey.length !== nacl.box.publicKeyLength || signature.length !== SIGNATURE_LENGTH) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
return xeddsaVerify(publicKey, message, signature);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
3
44
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export { CARRIER_ADDRESS_SIZE, CARRIER_PUBLIC_KEY_SIZE, carrierAddressFromPublic
|
|
|
3
3
|
export { PACKET_TYPE_FRIEND_REQUEST, PACKET_TYPE_MESSAGE, decodeCarrierPacket, encodeFriendMessagePacket, encodeFriendRequestPacket } from "./compat/packet.js";
|
|
4
4
|
export { CRYPTO_PACKET_DHTPK, CRYPTO_PACKET_FRIEND_REQ, CRYPTO_PACKET_NAT_PING, NET_PACKET_CRYPTO, createToxDhtCryptoRequest, openToxDhtCryptoRequest } from "./compat/tox-dht-crypto.js";
|
|
5
5
|
export { NET_PACKET_ONION_ANNOUNCE_REQUEST, NET_PACKET_ONION_ANNOUNCE_RESPONSE, NET_PACKET_ONION_DATA_REQUEST, NET_PACKET_ONION_DATA_RESPONSE, ONION_FRIEND_REQUEST_ID } from "./compat/tox-onion.js";
|
|
6
|
+
export { signDetached, verifyDetached, SIGNATURE_LENGTH } from "./crypto/sign.js";
|
|
7
|
+
export { base58ToBytes, bytesToBase58 } from "./utils/base58.js";
|
|
6
8
|
export { LegacyProtocolNotImplementedError } from "./runtime/errors.js";
|
|
7
9
|
export type { CarrierPacket, FriendMessagePacket, FriendRequestPacket } from "./compat/packet.js";
|
|
8
10
|
export type { ToxDhtCryptoRequest } from "./compat/tox-dht-crypto.js";
|
package/dist/index.js
CHANGED
|
@@ -3,4 +3,6 @@ export { CARRIER_ADDRESS_SIZE, CARRIER_PUBLIC_KEY_SIZE, carrierAddressFromPublic
|
|
|
3
3
|
export { PACKET_TYPE_FRIEND_REQUEST, PACKET_TYPE_MESSAGE, decodeCarrierPacket, encodeFriendMessagePacket, encodeFriendRequestPacket } from "./compat/packet.js";
|
|
4
4
|
export { CRYPTO_PACKET_DHTPK, CRYPTO_PACKET_FRIEND_REQ, CRYPTO_PACKET_NAT_PING, NET_PACKET_CRYPTO, createToxDhtCryptoRequest, openToxDhtCryptoRequest } from "./compat/tox-dht-crypto.js";
|
|
5
5
|
export { NET_PACKET_ONION_ANNOUNCE_REQUEST, NET_PACKET_ONION_ANNOUNCE_RESPONSE, NET_PACKET_ONION_DATA_REQUEST, NET_PACKET_ONION_DATA_RESPONSE, ONION_FRIEND_REQUEST_ID } from "./compat/tox-onion.js";
|
|
6
|
+
export { signDetached, verifyDetached, SIGNATURE_LENGTH } from "./crypto/sign.js";
|
|
7
|
+
export { base58ToBytes, bytesToBase58 } from "./utils/base58.js";
|
|
6
8
|
export { LegacyProtocolNotImplementedError } from "./runtime/errors.js";
|
package/dist/peer.d.ts
CHANGED
|
@@ -9,6 +9,14 @@ export declare class Peer {
|
|
|
9
9
|
stop(): Promise<void>;
|
|
10
10
|
pubkey(): string;
|
|
11
11
|
userid(): string;
|
|
12
|
+
/**
|
|
13
|
+
* Sign `message` with this identity's private key (XEdDSA over the X25519
|
|
14
|
+
* key). The signature verifies against the userid — `verifyDetached(
|
|
15
|
+
* base58Decode(userid), message, sig)` — so a relying party that knows only
|
|
16
|
+
* the userid can prove the signer holds the matching key. Used by "Sign in
|
|
17
|
+
* with Decent". Returns a detached 64-byte signature.
|
|
18
|
+
*/
|
|
19
|
+
sign(message: Uint8Array): Uint8Array;
|
|
12
20
|
address(): string;
|
|
13
21
|
joinNetwork(): Promise<BootstrapResult>;
|
|
14
22
|
lookup(pubkey: string): Promise<LookupResult>;
|
package/dist/peer.js
CHANGED
|
@@ -9,12 +9,13 @@ import { PACKET_TYPE_MESSAGE, PACKET_TYPE_FRIEND_REQUEST, PACKET_TYPE_USERINFO,
|
|
|
9
9
|
import { NET_PACKET_ONION_ANNOUNCE_RESPONSE, NET_PACKET_ONION_DATA_RESPONSE, ONION_FRIEND_REQUEST_ID, createOnionAnnounceRequest, createOnionDataPacket, createOnionDataRequest, createOnionRequest0, openOnionAnnounceResponse, openOnionDataPacket, openOnionDataResponse } from "./compat/tox-onion.js";
|
|
10
10
|
import { CRYPTO_PACKET_DHTPK, CRYPTO_PACKET_FRIEND_REQ, NET_PACKET_CRYPTO, createToxDhtCryptoRequest, openToxDhtCryptoRequest } from "./compat/tox-dht-crypto.js";
|
|
11
11
|
import { NET_PACKET_PING_REQUEST, NET_PACKET_PING_RESPONSE, NET_PACKET_GET_NODES, NET_PACKET_SEND_NODES, encodeDhtRpc, decodeDhtRpc, buildPingPlain, parsePingPlain, buildGetNodesPlain, parseGetNodesPlain, buildSendNodesPlain, parseSendNodesNodeBytes, packUdpNodeV4 } from "./compat/dht-rpc.js";
|
|
12
|
-
import { FileTransferManager } from "./compat/filetransfer.js";
|
|
12
|
+
import { FileTransferManager, PACKET_ID_FILE_SENDREQUEST, PACKET_ID_FILE_CONTROL, PACKET_ID_FILE_DATA } from "./compat/filetransfer.js";
|
|
13
13
|
import { NET_PACKET_CRYPTO_DATA, NET_PACKET_CRYPTO_HS, NET_PACKET_COOKIE_REQUEST, NET_PACKET_COOKIE_RESPONSE, createCookieRequest, createCookieResponse, createCryptoDataPacket, createCryptoHandshake, openCookieRequest, openCookieResponse, openCryptoDataPacket, openCryptoHandshake, incrementNonce } from "./compat/net-crypto.js";
|
|
14
14
|
import { LegacyExpressClient } from "./compat/express.js";
|
|
15
15
|
import { TcpRelayPool } from "./compat/tcp-relay-pool.js";
|
|
16
16
|
import { LegacyDhtClient } from "./compat/dht.js";
|
|
17
17
|
import { loadOrCreateKeyPair } from "./crypto/keypair.js";
|
|
18
|
+
import { signDetached } from "./crypto/sign.js";
|
|
18
19
|
import { base58ToBytes } from "./utils/base58.js";
|
|
19
20
|
import { UdpTransport } from "./transport/udp.js";
|
|
20
21
|
import { bytesToHex, concatBytes, randomBytes } from "./utils/bytes.js";
|
|
@@ -481,6 +482,19 @@ export class Peer {
|
|
|
481
482
|
}
|
|
482
483
|
return carrierIdFromPublicKey(this.#keyPair.publicKey);
|
|
483
484
|
}
|
|
485
|
+
/**
|
|
486
|
+
* Sign `message` with this identity's private key (XEdDSA over the X25519
|
|
487
|
+
* key). The signature verifies against the userid — `verifyDetached(
|
|
488
|
+
* base58Decode(userid), message, sig)` — so a relying party that knows only
|
|
489
|
+
* the userid can prove the signer holds the matching key. Used by "Sign in
|
|
490
|
+
* with Decent". Returns a detached 64-byte signature.
|
|
491
|
+
*/
|
|
492
|
+
sign(message) {
|
|
493
|
+
if (!this.#keyPair) {
|
|
494
|
+
throw new Error("Peer is not started");
|
|
495
|
+
}
|
|
496
|
+
return signDetached(this.#keyPair.secretKey, message);
|
|
497
|
+
}
|
|
484
498
|
address() {
|
|
485
499
|
if (!this.#keyPair) {
|
|
486
500
|
throw new Error("Peer is not started");
|
|
@@ -2771,8 +2785,23 @@ export class Peer {
|
|
|
2771
2785
|
// e.g. decentlan IP=163) stays bulk / single-transport to avoid the relay
|
|
2772
2786
|
// backlog + duplicate fan-out that high packet rates cause. Chat is sparse
|
|
2773
2787
|
// and net_crypto dedups by packet number, so dual-send is safe here.
|
|
2774
|
-
|
|
2775
|
-
|
|
2788
|
+
// File transfer (80 sendrequest / 81 control / 82 data) is ALSO treated as
|
|
2789
|
+
// bulk → single-transport. It is high-volume AND order-sensitive: the
|
|
2790
|
+
// receiver reassembles chunks SEQUENTIALLY (position is implicit) and there
|
|
2791
|
+
// is no retransmit queue. Dual-sending it over UDP + relay (two paths with
|
|
2792
|
+
// different latency) reorders the chunks, and the receive-side low-water
|
|
2793
|
+
// dedup then drops the late copies — so a large file (mp4) gets gaps /
|
|
2794
|
+
// wrong order and never completes, while a tiny one (png, 1-2 chunks)
|
|
2795
|
+
// happens to finish before reordering bites. Pinning file transfer to a
|
|
2796
|
+
// single transport keeps the chunks in send order. (Reliability across a
|
|
2797
|
+
// lossy path still wants a reorder buffer + retransmit — tracked separately.)
|
|
2798
|
+
const isFileTransfer = kind === PACKET_ID_FILE_SENDREQUEST || kind === PACKET_ID_FILE_CONTROL || kind === PACKET_ID_FILE_DATA;
|
|
2799
|
+
const isBulkData = (this.#opts.bulkDataPacketId !== undefined && kind === this.#opts.bulkDataPacketId) || isFileTransfer;
|
|
2800
|
+
// File transfer rides the relay RELIABLY (never dropped under backpressure):
|
|
2801
|
+
// a dropped IP/CCTV packet is fine (the stream moves on), but a dropped file
|
|
2802
|
+
// chunk corrupts the file (no retransmit). So bulk IP data stays droppable
|
|
2803
|
+
// while file transfer is queued.
|
|
2804
|
+
await this.#sendToFriend(friendId, encrypted, session, isBulkData, isFileTransfer);
|
|
2776
2805
|
}
|
|
2777
2806
|
/**
|
|
2778
2807
|
* Send `packet` to `friendId` over whichever transport(s) are
|
|
@@ -2782,7 +2811,7 @@ export class Peer {
|
|
|
2782
2811
|
* receiving end (toxcore does the same when both transports are up).
|
|
2783
2812
|
* Throws only if zero transports succeeded.
|
|
2784
2813
|
*/
|
|
2785
|
-
async #sendToFriend(friendId, packet, session, isBulkData = false) {
|
|
2814
|
+
async #sendToFriend(friendId, packet, session, isBulkData = false, reliableOnRelay = false) {
|
|
2786
2815
|
const s = session ?? this.#friendSessions.get(friendId);
|
|
2787
2816
|
let udpOk = false;
|
|
2788
2817
|
let tcpOk = false;
|
|
@@ -2862,7 +2891,9 @@ export class Peer {
|
|
|
2862
2891
|
// Bulk IP data is droppable over the relay: under backpressure the
|
|
2863
2892
|
// relay client drops it (bounded queue) rather than buffering seconds
|
|
2864
2893
|
// of packets. Control frames pass isBulkData=false and always queue.
|
|
2865
|
-
|
|
2894
|
+
// File transfer is bulk (single-transport routing) but must NOT be
|
|
2895
|
+
// dropped — a lost chunk corrupts the file — so it is queued reliably.
|
|
2896
|
+
const sent = this.#tcpRelays.sendToFriend(tcpKey, packet, isBulkData && !reliableOnRelay);
|
|
2866
2897
|
if (sent > 0) {
|
|
2867
2898
|
tcpOk = true;
|
|
2868
2899
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.48",
|
|
4
4
|
"description": "Pure TypeScript port of Elastos Carrier (toxcore-derived) P2P messaging. DHT, onion routing, TCP relay, FlatBuffers app payloads, Express offline relay. Wire-compatible with iOS Beagle and the Carrier C SDK.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
74
|
"flatbuffers": "^25.9.23",
|
|
75
|
-
"tweetnacl": "^1.0.3"
|
|
75
|
+
"tweetnacl": "^1.0.3",
|
|
76
|
+
"curve25519-js": "^0.0.4"
|
|
76
77
|
}
|
|
77
78
|
}
|