@decentnetwork/peer 0.1.45 → 0.1.47
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 +51 -0
- 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
|
@@ -15,6 +15,7 @@ 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");
|
|
@@ -3814,6 +3828,27 @@ export class Peer {
|
|
|
3814
3828
|
if (typeof this.#dhtMaintenanceTimer.unref === "function")
|
|
3815
3829
|
this.#dhtMaintenanceTimer.unref();
|
|
3816
3830
|
void this.#doDhtMaintenance().catch(() => undefined); // become findable right after join
|
|
3831
|
+
// Fast initial population. The steady 15s cadence converges far too
|
|
3832
|
+
// slowly from a cold table — a fresh peer sat at ~14 known nodes, which
|
|
3833
|
+
// is too sparse for the onion announce to store on the nodes truly
|
|
3834
|
+
// closest to our key (the ones native clients query when searching for
|
|
3835
|
+
// us). Run a few quick rounds up front to fill the table within seconds
|
|
3836
|
+
// so the announce can reach storing nodes and we become discoverable.
|
|
3837
|
+
for (const delay of [1200, 3000, 5500, 9000, 13000]) {
|
|
3838
|
+
const t = setTimeout(() => void this.#doDhtMaintenance().catch(() => undefined), delay);
|
|
3839
|
+
if (typeof t.unref === "function")
|
|
3840
|
+
t.unref();
|
|
3841
|
+
}
|
|
3842
|
+
// The join-time announce ran against a near-empty table, so it stored on
|
|
3843
|
+
// few/no nodes. Re-announce once the burst above has populated the table
|
|
3844
|
+
// (~10s) so our FIRST useful announce lands on the genuinely-closest
|
|
3845
|
+
// nodes — the difference between "discoverable within seconds" and "not
|
|
3846
|
+
// until the 20s steady-state timer happens to converge".
|
|
3847
|
+
for (const delay of [10000, 18000]) {
|
|
3848
|
+
const a = setTimeout(() => void this.#runSelfAnnounce(true, Date.now() + 8000).catch(() => undefined), delay);
|
|
3849
|
+
if (typeof a.unref === "function")
|
|
3850
|
+
a.unref();
|
|
3851
|
+
}
|
|
3817
3852
|
}
|
|
3818
3853
|
async #doDhtMaintenance() {
|
|
3819
3854
|
if (!this.#keyPair)
|
|
@@ -3824,6 +3859,22 @@ export class Peer {
|
|
|
3824
3859
|
for (const node of this.#closestKnownNodes(selfPk, 8)) {
|
|
3825
3860
|
void this.#sendDhtGetNodes(node, selfPk);
|
|
3826
3861
|
}
|
|
3862
|
+
// 1b. Fill the routing table ACROSS the keyspace via random-target
|
|
3863
|
+
// searches. toxcore keeps random DHT search slots so the table isn't
|
|
3864
|
+
// clustered only near self; without this a JS peer's table stayed tiny
|
|
3865
|
+
// (~14-97 nodes) and the onion announce could only store on a sparse,
|
|
3866
|
+
// far-from-closest subset that native searchers never query — so they
|
|
3867
|
+
// couldn't discover the JS peer by address. A fuller table lets the
|
|
3868
|
+
// announce reach the genuinely-closest nodes. Search harder while the
|
|
3869
|
+
// table is small, then taper to a steady refresh.
|
|
3870
|
+
const small = this.#knownNodes.length < 150;
|
|
3871
|
+
const randomSearches = this.#knownNodes.length < 60 ? 6 : small ? 4 : 2;
|
|
3872
|
+
for (let i = 0; i < randomSearches; i++) {
|
|
3873
|
+
const target = randomBytes(32);
|
|
3874
|
+
for (const node of this.#closestKnownNodes(target, small ? 6 : 4)) {
|
|
3875
|
+
void this.#sendDhtGetNodes(node, target);
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3827
3878
|
// 2. For each friend not on a confirmed UDP path, get_nodes toward THEIR
|
|
3828
3879
|
// DHT key → learn their UDP endpoint → punch from our side.
|
|
3829
3880
|
for (const [, session] of this.#friendSessions.entries()) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.47",
|
|
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
|
}
|