tether-name 1.0.0
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/LICENSE +21 -0
- package/README.md +210 -0
- package/dist/index.d.mts +133 -0
- package/dist/index.d.ts +133 -0
- package/dist/index.js +271 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +238 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Commit 451
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# tether-name
|
|
2
|
+
|
|
3
|
+
Official Node.js SDK for [tether.name](https://tether.name) — cryptographic identity verification for AI agents. Tether lets AI agents prove their identity using RSA-2048 signatures, enabling trusted agent-to-agent communication.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install tether-name
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js 18+ (uses native `fetch` and `crypto` modules).
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { TetherClient } from 'tether-name';
|
|
17
|
+
|
|
18
|
+
const client = new TetherClient({
|
|
19
|
+
credentialId: 'rgUOzbqar8z0Ag9RZH5I',
|
|
20
|
+
privateKeyPath: '/path/to/your/private-key.der'
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// One-call verification
|
|
24
|
+
const result = await client.verify();
|
|
25
|
+
console.log(result.verified); // true
|
|
26
|
+
console.log(result.agentName); // "Jawnnybot"
|
|
27
|
+
console.log(result.verifyUrl); // "https://tether.name/check?challenge=..."
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Step-by-Step Usage
|
|
31
|
+
|
|
32
|
+
For more control over the verification process:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { TetherClient } from 'tether-name';
|
|
36
|
+
|
|
37
|
+
const client = new TetherClient({
|
|
38
|
+
credentialId: 'rgUOzbqar8z0Ag9RZH5I',
|
|
39
|
+
privateKeyPem: `-----BEGIN RSA PRIVATE KEY-----
|
|
40
|
+
MIIEpAIBAAKCAQEA...
|
|
41
|
+
-----END RSA PRIVATE KEY-----`
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
// 1. Request a challenge from Tether
|
|
46
|
+
const challenge = await client.requestChallenge();
|
|
47
|
+
|
|
48
|
+
// 2. Sign the challenge with your private key
|
|
49
|
+
const proof = client.sign(challenge);
|
|
50
|
+
|
|
51
|
+
// 3. Submit the proof for verification
|
|
52
|
+
const result = await client.submitProof(challenge, proof);
|
|
53
|
+
|
|
54
|
+
if (result.verified) {
|
|
55
|
+
console.log(`✅ Verified as ${result.agentName}`);
|
|
56
|
+
console.log(`📝 Public verification: ${result.verifyUrl}`);
|
|
57
|
+
} else {
|
|
58
|
+
console.log(`❌ Verification failed: ${result.error}`);
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error('Verification error:', error.message);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Configuration Options
|
|
66
|
+
|
|
67
|
+
### Constructor Options
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
interface TetherClientConfig {
|
|
71
|
+
// Credential ID (required)
|
|
72
|
+
credentialId?: string; // Or use TETHER_CREDENTIAL_ID env var
|
|
73
|
+
|
|
74
|
+
// Private key (choose one)
|
|
75
|
+
privateKeyPath?: string; // Path to DER or PEM file
|
|
76
|
+
privateKeyPem?: string; // PEM string directly
|
|
77
|
+
privateKeyBuffer?: Buffer; // DER buffer directly
|
|
78
|
+
|
|
79
|
+
// Optional
|
|
80
|
+
baseUrl?: string; // API base URL (defaults to https://api.tether.name)
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Key Format Support
|
|
85
|
+
|
|
86
|
+
The SDK supports both DER and PEM private key formats:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// From file path (auto-detects format)
|
|
90
|
+
const client1 = new TetherClient({
|
|
91
|
+
credentialId: 'your-id',
|
|
92
|
+
privateKeyPath: '/path/to/key.der' // or .pem
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// From PEM string
|
|
96
|
+
const client2 = new TetherClient({
|
|
97
|
+
credentialId: 'your-id',
|
|
98
|
+
privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\n...'
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// From DER buffer
|
|
102
|
+
const derBuffer = fs.readFileSync('/path/to/key.der');
|
|
103
|
+
const client3 = new TetherClient({
|
|
104
|
+
credentialId: 'your-id',
|
|
105
|
+
privateKeyBuffer: derBuffer
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Environment Variables
|
|
110
|
+
|
|
111
|
+
Set these environment variables to avoid hardcoding credentials:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
export TETHER_CREDENTIAL_ID="your-credential-id"
|
|
115
|
+
export TETHER_PRIVATE_KEY_PATH="/path/to/your/private-key.der"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Then initialize without parameters:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
const client = new TetherClient({}); // Uses env vars
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## API Reference
|
|
125
|
+
|
|
126
|
+
### `TetherClient`
|
|
127
|
+
|
|
128
|
+
#### `constructor(config: TetherClientConfig)`
|
|
129
|
+
|
|
130
|
+
Creates a new Tether client instance.
|
|
131
|
+
|
|
132
|
+
#### `async verify(): Promise<VerificationResult>`
|
|
133
|
+
|
|
134
|
+
Performs complete verification in one call. Requests challenge, signs it, and submits proof.
|
|
135
|
+
|
|
136
|
+
**Throws:** `TetherVerificationError` if verification fails.
|
|
137
|
+
|
|
138
|
+
#### `async requestChallenge(): Promise<string>`
|
|
139
|
+
|
|
140
|
+
Requests a new challenge from the Tether API.
|
|
141
|
+
|
|
142
|
+
**Returns:** Challenge string to be signed.
|
|
143
|
+
|
|
144
|
+
#### `sign(challenge: string): string`
|
|
145
|
+
|
|
146
|
+
Signs a challenge using the configured private key.
|
|
147
|
+
|
|
148
|
+
**Returns:** URL-safe base64 signature (no padding).
|
|
149
|
+
|
|
150
|
+
#### `async submitProof(challenge: string, proof: string): Promise<VerificationResult>`
|
|
151
|
+
|
|
152
|
+
Submits signed proof to verify the challenge.
|
|
153
|
+
|
|
154
|
+
### Types
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
interface VerificationResult {
|
|
158
|
+
verified: boolean; // Whether verification succeeded
|
|
159
|
+
agentName?: string; // Registered agent name
|
|
160
|
+
verifyUrl?: string; // Public verification URL
|
|
161
|
+
email?: string; // Registered email
|
|
162
|
+
registeredSince?: string; // ISO date of registration
|
|
163
|
+
error?: string; // Error message if failed
|
|
164
|
+
challenge?: string; // The verified challenge
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Errors
|
|
169
|
+
|
|
170
|
+
- `TetherError` - Base error class
|
|
171
|
+
- `TetherVerificationError` - Verification failed
|
|
172
|
+
- `TetherAPIError` - API request failed
|
|
173
|
+
|
|
174
|
+
## Getting Your Credentials
|
|
175
|
+
|
|
176
|
+
1. Visit [tether.name](https://tether.name)
|
|
177
|
+
2. Register your agent and get a credential ID
|
|
178
|
+
3. Generate an RSA-2048 private key:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# Generate private key
|
|
182
|
+
openssl genrsa -out private-key.pem 2048
|
|
183
|
+
|
|
184
|
+
# Convert to DER format (optional)
|
|
185
|
+
openssl rsa -in private-key.pem -outform DER -out private-key.der
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Requirements
|
|
189
|
+
|
|
190
|
+
- Node.js 18+ (uses native `fetch`)
|
|
191
|
+
- RSA-2048 private key
|
|
192
|
+
- Zero runtime dependencies (uses only Node.js built-ins)
|
|
193
|
+
|
|
194
|
+
## Security Notes
|
|
195
|
+
|
|
196
|
+
- Keep your private key secure and never commit it to version control
|
|
197
|
+
- Use environment variables or secure key management
|
|
198
|
+
- The SDK uses SHA256withRSA signatures with URL-safe base64 encoding
|
|
199
|
+
- All verification happens server-side at tether.name
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
204
|
+
|
|
205
|
+
## Links
|
|
206
|
+
|
|
207
|
+
- 🌐 [Tether Website](https://tether.name)
|
|
208
|
+
- 📘 [Documentation](https://tether.name/docs)
|
|
209
|
+
- 🐛 [Issues](https://github.com/Commit451/tether-name-node/issues)
|
|
210
|
+
- 📦 [npm Package](https://www.npmjs.com/package/tether-name)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { KeyObject } from 'crypto';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for TetherClient
|
|
5
|
+
*/
|
|
6
|
+
interface TetherClientConfig {
|
|
7
|
+
/** The credential ID for the agent */
|
|
8
|
+
credentialId?: string;
|
|
9
|
+
/** Path to the private key file (DER or PEM format) */
|
|
10
|
+
privateKeyPath?: string;
|
|
11
|
+
/** Private key as a string (PEM format) */
|
|
12
|
+
privateKeyPem?: string;
|
|
13
|
+
/** Private key as a Buffer (DER format) */
|
|
14
|
+
privateKeyBuffer?: Buffer;
|
|
15
|
+
/** Base URL for the Tether API (defaults to https://api.tether.name) */
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Response from the challenge request endpoint
|
|
20
|
+
*/
|
|
21
|
+
interface ChallengeResponse {
|
|
22
|
+
code: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Request payload for challenge verification
|
|
26
|
+
*/
|
|
27
|
+
interface VerificationRequest {
|
|
28
|
+
challenge: string;
|
|
29
|
+
proof: string;
|
|
30
|
+
credentialId: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Response from the challenge verification endpoint
|
|
34
|
+
*/
|
|
35
|
+
interface VerificationResponse {
|
|
36
|
+
valid: boolean;
|
|
37
|
+
verifyUrl?: string;
|
|
38
|
+
agentName?: string;
|
|
39
|
+
email?: string;
|
|
40
|
+
registeredSince?: string;
|
|
41
|
+
error?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Result of a tether verification attempt
|
|
45
|
+
*/
|
|
46
|
+
interface VerificationResult {
|
|
47
|
+
/** Whether the verification was successful */
|
|
48
|
+
verified: boolean;
|
|
49
|
+
/** The agent's registered name */
|
|
50
|
+
agentName?: string;
|
|
51
|
+
/** Public verification URL */
|
|
52
|
+
verifyUrl?: string;
|
|
53
|
+
/** The agent's registered email */
|
|
54
|
+
email?: string;
|
|
55
|
+
/** ISO date string of when the agent was registered */
|
|
56
|
+
registeredSince?: string;
|
|
57
|
+
/** Error message if verification failed */
|
|
58
|
+
error?: string;
|
|
59
|
+
/** The challenge that was verified */
|
|
60
|
+
challenge?: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Supported private key formats
|
|
64
|
+
*/
|
|
65
|
+
type KeyFormat = 'pem' | 'der';
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* TetherClient - Official SDK for tether.name agent identity verification
|
|
69
|
+
*/
|
|
70
|
+
declare class TetherClient {
|
|
71
|
+
private readonly credentialId;
|
|
72
|
+
private readonly privateKey;
|
|
73
|
+
private readonly baseUrl;
|
|
74
|
+
constructor(config: TetherClientConfig);
|
|
75
|
+
/**
|
|
76
|
+
* Request a challenge from the Tether API
|
|
77
|
+
*/
|
|
78
|
+
requestChallenge(): Promise<string>;
|
|
79
|
+
/**
|
|
80
|
+
* Sign a challenge string
|
|
81
|
+
*/
|
|
82
|
+
sign(challenge: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* Submit proof for a challenge
|
|
85
|
+
*/
|
|
86
|
+
submitProof(challenge: string, proof: string): Promise<VerificationResult>;
|
|
87
|
+
/**
|
|
88
|
+
* Perform complete verification in one call
|
|
89
|
+
*/
|
|
90
|
+
verify(): Promise<VerificationResult>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Base error class for all Tether-related errors
|
|
95
|
+
*/
|
|
96
|
+
declare class TetherError extends Error {
|
|
97
|
+
readonly cause?: Error | undefined;
|
|
98
|
+
constructor(message: string, cause?: Error | undefined);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Error thrown when verification fails
|
|
102
|
+
*/
|
|
103
|
+
declare class TetherVerificationError extends TetherError {
|
|
104
|
+
constructor(message: string, cause?: Error);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Error thrown when API requests fail
|
|
108
|
+
*/
|
|
109
|
+
declare class TetherAPIError extends TetherError {
|
|
110
|
+
readonly status?: number | undefined;
|
|
111
|
+
readonly response?: string | undefined;
|
|
112
|
+
constructor(message: string, status?: number | undefined, response?: string | undefined, cause?: Error);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Loads a private key from various sources
|
|
117
|
+
*/
|
|
118
|
+
declare function loadPrivateKey(options: {
|
|
119
|
+
keyPath?: string;
|
|
120
|
+
keyPem?: string;
|
|
121
|
+
keyBuffer?: Buffer;
|
|
122
|
+
}): KeyObject;
|
|
123
|
+
/**
|
|
124
|
+
* Signs a challenge string using RSA-SHA256
|
|
125
|
+
* Returns URL-safe base64 encoded signature (no padding)
|
|
126
|
+
*/
|
|
127
|
+
declare function signChallenge(privateKey: KeyObject, challenge: string): string;
|
|
128
|
+
/**
|
|
129
|
+
* Utility function to detect key format from file extension or content
|
|
130
|
+
*/
|
|
131
|
+
declare function detectKeyFormat(keyPath: string): KeyFormat;
|
|
132
|
+
|
|
133
|
+
export { type ChallengeResponse, type KeyFormat, TetherAPIError, TetherClient, type TetherClientConfig, TetherError, TetherVerificationError, type VerificationRequest, type VerificationResponse, type VerificationResult, detectKeyFormat, loadPrivateKey, signChallenge };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { KeyObject } from 'crypto';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for TetherClient
|
|
5
|
+
*/
|
|
6
|
+
interface TetherClientConfig {
|
|
7
|
+
/** The credential ID for the agent */
|
|
8
|
+
credentialId?: string;
|
|
9
|
+
/** Path to the private key file (DER or PEM format) */
|
|
10
|
+
privateKeyPath?: string;
|
|
11
|
+
/** Private key as a string (PEM format) */
|
|
12
|
+
privateKeyPem?: string;
|
|
13
|
+
/** Private key as a Buffer (DER format) */
|
|
14
|
+
privateKeyBuffer?: Buffer;
|
|
15
|
+
/** Base URL for the Tether API (defaults to https://api.tether.name) */
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Response from the challenge request endpoint
|
|
20
|
+
*/
|
|
21
|
+
interface ChallengeResponse {
|
|
22
|
+
code: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Request payload for challenge verification
|
|
26
|
+
*/
|
|
27
|
+
interface VerificationRequest {
|
|
28
|
+
challenge: string;
|
|
29
|
+
proof: string;
|
|
30
|
+
credentialId: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Response from the challenge verification endpoint
|
|
34
|
+
*/
|
|
35
|
+
interface VerificationResponse {
|
|
36
|
+
valid: boolean;
|
|
37
|
+
verifyUrl?: string;
|
|
38
|
+
agentName?: string;
|
|
39
|
+
email?: string;
|
|
40
|
+
registeredSince?: string;
|
|
41
|
+
error?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Result of a tether verification attempt
|
|
45
|
+
*/
|
|
46
|
+
interface VerificationResult {
|
|
47
|
+
/** Whether the verification was successful */
|
|
48
|
+
verified: boolean;
|
|
49
|
+
/** The agent's registered name */
|
|
50
|
+
agentName?: string;
|
|
51
|
+
/** Public verification URL */
|
|
52
|
+
verifyUrl?: string;
|
|
53
|
+
/** The agent's registered email */
|
|
54
|
+
email?: string;
|
|
55
|
+
/** ISO date string of when the agent was registered */
|
|
56
|
+
registeredSince?: string;
|
|
57
|
+
/** Error message if verification failed */
|
|
58
|
+
error?: string;
|
|
59
|
+
/** The challenge that was verified */
|
|
60
|
+
challenge?: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Supported private key formats
|
|
64
|
+
*/
|
|
65
|
+
type KeyFormat = 'pem' | 'der';
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* TetherClient - Official SDK for tether.name agent identity verification
|
|
69
|
+
*/
|
|
70
|
+
declare class TetherClient {
|
|
71
|
+
private readonly credentialId;
|
|
72
|
+
private readonly privateKey;
|
|
73
|
+
private readonly baseUrl;
|
|
74
|
+
constructor(config: TetherClientConfig);
|
|
75
|
+
/**
|
|
76
|
+
* Request a challenge from the Tether API
|
|
77
|
+
*/
|
|
78
|
+
requestChallenge(): Promise<string>;
|
|
79
|
+
/**
|
|
80
|
+
* Sign a challenge string
|
|
81
|
+
*/
|
|
82
|
+
sign(challenge: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* Submit proof for a challenge
|
|
85
|
+
*/
|
|
86
|
+
submitProof(challenge: string, proof: string): Promise<VerificationResult>;
|
|
87
|
+
/**
|
|
88
|
+
* Perform complete verification in one call
|
|
89
|
+
*/
|
|
90
|
+
verify(): Promise<VerificationResult>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Base error class for all Tether-related errors
|
|
95
|
+
*/
|
|
96
|
+
declare class TetherError extends Error {
|
|
97
|
+
readonly cause?: Error | undefined;
|
|
98
|
+
constructor(message: string, cause?: Error | undefined);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Error thrown when verification fails
|
|
102
|
+
*/
|
|
103
|
+
declare class TetherVerificationError extends TetherError {
|
|
104
|
+
constructor(message: string, cause?: Error);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Error thrown when API requests fail
|
|
108
|
+
*/
|
|
109
|
+
declare class TetherAPIError extends TetherError {
|
|
110
|
+
readonly status?: number | undefined;
|
|
111
|
+
readonly response?: string | undefined;
|
|
112
|
+
constructor(message: string, status?: number | undefined, response?: string | undefined, cause?: Error);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Loads a private key from various sources
|
|
117
|
+
*/
|
|
118
|
+
declare function loadPrivateKey(options: {
|
|
119
|
+
keyPath?: string;
|
|
120
|
+
keyPem?: string;
|
|
121
|
+
keyBuffer?: Buffer;
|
|
122
|
+
}): KeyObject;
|
|
123
|
+
/**
|
|
124
|
+
* Signs a challenge string using RSA-SHA256
|
|
125
|
+
* Returns URL-safe base64 encoded signature (no padding)
|
|
126
|
+
*/
|
|
127
|
+
declare function signChallenge(privateKey: KeyObject, challenge: string): string;
|
|
128
|
+
/**
|
|
129
|
+
* Utility function to detect key format from file extension or content
|
|
130
|
+
*/
|
|
131
|
+
declare function detectKeyFormat(keyPath: string): KeyFormat;
|
|
132
|
+
|
|
133
|
+
export { type ChallengeResponse, type KeyFormat, TetherAPIError, TetherClient, type TetherClientConfig, TetherError, TetherVerificationError, type VerificationRequest, type VerificationResponse, type VerificationResult, detectKeyFormat, loadPrivateKey, signChallenge };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
TetherAPIError: () => TetherAPIError,
|
|
24
|
+
TetherClient: () => TetherClient,
|
|
25
|
+
TetherError: () => TetherError,
|
|
26
|
+
TetherVerificationError: () => TetherVerificationError,
|
|
27
|
+
detectKeyFormat: () => detectKeyFormat,
|
|
28
|
+
loadPrivateKey: () => loadPrivateKey,
|
|
29
|
+
signChallenge: () => signChallenge
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
|
|
33
|
+
// src/errors.ts
|
|
34
|
+
var TetherError = class _TetherError extends Error {
|
|
35
|
+
constructor(message, cause) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.cause = cause;
|
|
38
|
+
this.name = "TetherError";
|
|
39
|
+
if (Error.captureStackTrace) {
|
|
40
|
+
Error.captureStackTrace(this, _TetherError);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var TetherVerificationError = class extends TetherError {
|
|
45
|
+
constructor(message, cause) {
|
|
46
|
+
super(message, cause);
|
|
47
|
+
this.name = "TetherVerificationError";
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var TetherAPIError = class extends TetherError {
|
|
51
|
+
constructor(message, status, response, cause) {
|
|
52
|
+
super(message, cause);
|
|
53
|
+
this.status = status;
|
|
54
|
+
this.response = response;
|
|
55
|
+
this.name = "TetherAPIError";
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// src/crypto.ts
|
|
60
|
+
var import_crypto = require("crypto");
|
|
61
|
+
var import_fs = require("fs");
|
|
62
|
+
function loadPrivateKey(options) {
|
|
63
|
+
const { keyPath, keyPem, keyBuffer } = options;
|
|
64
|
+
try {
|
|
65
|
+
if (keyPem) {
|
|
66
|
+
return (0, import_crypto.createPrivateKey)(keyPem);
|
|
67
|
+
}
|
|
68
|
+
if (keyBuffer) {
|
|
69
|
+
return (0, import_crypto.createPrivateKey)({
|
|
70
|
+
key: keyBuffer,
|
|
71
|
+
format: "der",
|
|
72
|
+
type: "pkcs1"
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (keyPath) {
|
|
76
|
+
const keyData = (0, import_fs.readFileSync)(keyPath);
|
|
77
|
+
if (keyPath.endsWith(".pem") || keyData.toString().includes("-----BEGIN")) {
|
|
78
|
+
return (0, import_crypto.createPrivateKey)(keyData);
|
|
79
|
+
} else {
|
|
80
|
+
return (0, import_crypto.createPrivateKey)({
|
|
81
|
+
key: keyData,
|
|
82
|
+
format: "der",
|
|
83
|
+
type: "pkcs1"
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
throw new TetherError("No private key provided");
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (error instanceof TetherError) {
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
throw new TetherError(
|
|
93
|
+
`Failed to load private key: ${error instanceof Error ? error.message : String(error)}`,
|
|
94
|
+
error instanceof Error ? error : void 0
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function signChallenge(privateKey, challenge) {
|
|
99
|
+
try {
|
|
100
|
+
const sign = (0, import_crypto.createSign)("SHA256");
|
|
101
|
+
sign.update(challenge);
|
|
102
|
+
sign.end();
|
|
103
|
+
const signature = sign.sign(privateKey);
|
|
104
|
+
return signature.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
105
|
+
} catch (error) {
|
|
106
|
+
throw new TetherError(
|
|
107
|
+
`Failed to sign challenge: ${error instanceof Error ? error.message : String(error)}`,
|
|
108
|
+
error instanceof Error ? error : void 0
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function detectKeyFormat(keyPath) {
|
|
113
|
+
if (keyPath.endsWith(".pem")) {
|
|
114
|
+
return "pem";
|
|
115
|
+
}
|
|
116
|
+
if (keyPath.endsWith(".der")) {
|
|
117
|
+
return "der";
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const keyData = (0, import_fs.readFileSync)(keyPath, { encoding: "utf8", flag: "r" });
|
|
121
|
+
if (keyData.includes("-----BEGIN")) {
|
|
122
|
+
return "pem";
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
}
|
|
126
|
+
return "der";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/client.ts
|
|
130
|
+
var TetherClient = class {
|
|
131
|
+
credentialId;
|
|
132
|
+
privateKey;
|
|
133
|
+
baseUrl;
|
|
134
|
+
constructor(config) {
|
|
135
|
+
this.credentialId = config.credentialId || process.env.TETHER_CREDENTIAL_ID || "";
|
|
136
|
+
if (!this.credentialId) {
|
|
137
|
+
throw new TetherError("Credential ID is required. Provide it in config or set TETHER_CREDENTIAL_ID environment variable.");
|
|
138
|
+
}
|
|
139
|
+
const keyPath = config.privateKeyPath || process.env.TETHER_PRIVATE_KEY_PATH;
|
|
140
|
+
this.privateKey = loadPrivateKey({
|
|
141
|
+
keyPath,
|
|
142
|
+
keyPem: config.privateKeyPem,
|
|
143
|
+
keyBuffer: config.privateKeyBuffer
|
|
144
|
+
});
|
|
145
|
+
this.baseUrl = config.baseUrl || "https://api.tether.name";
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Request a challenge from the Tether API
|
|
149
|
+
*/
|
|
150
|
+
async requestChallenge() {
|
|
151
|
+
try {
|
|
152
|
+
const response = await fetch(`${this.baseUrl}/challenge`, {
|
|
153
|
+
method: "POST",
|
|
154
|
+
headers: {
|
|
155
|
+
"Content-Type": "application/json"
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
if (!response.ok) {
|
|
159
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
160
|
+
throw new TetherAPIError(
|
|
161
|
+
`Challenge request failed: ${response.status} ${response.statusText}`,
|
|
162
|
+
response.status,
|
|
163
|
+
errorText
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
const data = await response.json();
|
|
167
|
+
if (!data.code) {
|
|
168
|
+
throw new TetherAPIError("Invalid challenge response: missing code");
|
|
169
|
+
}
|
|
170
|
+
return data.code;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (error instanceof TetherError) {
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
throw new TetherAPIError(
|
|
176
|
+
`Failed to request challenge: ${error instanceof Error ? error.message : String(error)}`,
|
|
177
|
+
void 0,
|
|
178
|
+
void 0,
|
|
179
|
+
error instanceof Error ? error : void 0
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Sign a challenge string
|
|
185
|
+
*/
|
|
186
|
+
sign(challenge) {
|
|
187
|
+
return signChallenge(this.privateKey, challenge);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Submit proof for a challenge
|
|
191
|
+
*/
|
|
192
|
+
async submitProof(challenge, proof) {
|
|
193
|
+
try {
|
|
194
|
+
const payload = {
|
|
195
|
+
challenge,
|
|
196
|
+
proof,
|
|
197
|
+
credentialId: this.credentialId
|
|
198
|
+
};
|
|
199
|
+
const response = await fetch(`${this.baseUrl}/challenge/verify`, {
|
|
200
|
+
method: "POST",
|
|
201
|
+
headers: {
|
|
202
|
+
"Content-Type": "application/json"
|
|
203
|
+
},
|
|
204
|
+
body: JSON.stringify(payload)
|
|
205
|
+
});
|
|
206
|
+
if (!response.ok) {
|
|
207
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
208
|
+
throw new TetherAPIError(
|
|
209
|
+
`Verification failed: ${response.status} ${response.statusText}`,
|
|
210
|
+
response.status,
|
|
211
|
+
errorText
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
const data = await response.json();
|
|
215
|
+
return {
|
|
216
|
+
verified: data.valid,
|
|
217
|
+
agentName: data.agentName,
|
|
218
|
+
verifyUrl: data.verifyUrl,
|
|
219
|
+
email: data.email,
|
|
220
|
+
registeredSince: data.registeredSince,
|
|
221
|
+
error: data.error,
|
|
222
|
+
challenge
|
|
223
|
+
};
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (error instanceof TetherError) {
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
throw new TetherAPIError(
|
|
229
|
+
`Failed to submit proof: ${error instanceof Error ? error.message : String(error)}`,
|
|
230
|
+
void 0,
|
|
231
|
+
void 0,
|
|
232
|
+
error instanceof Error ? error : void 0
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Perform complete verification in one call
|
|
238
|
+
*/
|
|
239
|
+
async verify() {
|
|
240
|
+
try {
|
|
241
|
+
const challenge = await this.requestChallenge();
|
|
242
|
+
const proof = this.sign(challenge);
|
|
243
|
+
const result = await this.submitProof(challenge, proof);
|
|
244
|
+
if (!result.verified) {
|
|
245
|
+
throw new TetherVerificationError(
|
|
246
|
+
result.error || "Verification failed for unknown reason"
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
return result;
|
|
250
|
+
} catch (error) {
|
|
251
|
+
if (error instanceof TetherError) {
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
throw new TetherVerificationError(
|
|
255
|
+
`Verification failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
256
|
+
error instanceof Error ? error : void 0
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
262
|
+
0 && (module.exports = {
|
|
263
|
+
TetherAPIError,
|
|
264
|
+
TetherClient,
|
|
265
|
+
TetherError,
|
|
266
|
+
TetherVerificationError,
|
|
267
|
+
detectKeyFormat,
|
|
268
|
+
loadPrivateKey,
|
|
269
|
+
signChallenge
|
|
270
|
+
});
|
|
271
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/crypto.ts","../src/client.ts"],"sourcesContent":["/**\n * Tether Name SDK - Official Node.js library for tether.name agent identity verification\n * \n * @example\n * ```typescript\n * import { TetherClient } from 'tether-name';\n * \n * const client = new TetherClient({\n * credentialId: 'your-credential-id',\n * privateKeyPath: '/path/to/key.der'\n * });\n * \n * const result = await client.verify();\n * console.log(result.verified, result.agentName);\n * ```\n */\n\n// Main exports\nexport { TetherClient } from './client.js';\n\n// Types\nexport type {\n TetherClientConfig,\n ChallengeResponse,\n VerificationRequest,\n VerificationResponse,\n VerificationResult,\n KeyFormat\n} from './types.js';\n\n// Errors\nexport {\n TetherError,\n TetherAPIError,\n TetherVerificationError\n} from './errors.js';\n\n// Crypto utilities (for advanced use cases)\nexport {\n loadPrivateKey,\n signChallenge,\n detectKeyFormat\n} from './crypto.js';","/**\n * Base error class for all Tether-related errors\n */\nexport class TetherError extends Error {\n constructor(message: string, public readonly cause?: Error) {\n super(message);\n this.name = 'TetherError';\n \n // Maintain proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, TetherError);\n }\n }\n}\n\n/**\n * Error thrown when verification fails\n */\nexport class TetherVerificationError extends TetherError {\n constructor(message: string, cause?: Error) {\n super(message, cause);\n this.name = 'TetherVerificationError';\n }\n}\n\n/**\n * Error thrown when API requests fail\n */\nexport class TetherAPIError extends TetherError {\n constructor(\n message: string,\n public readonly status?: number,\n public readonly response?: string,\n cause?: Error\n ) {\n super(message, cause);\n this.name = 'TetherAPIError';\n }\n}","import { createSign, createPrivateKey, KeyObject } from 'crypto';\nimport { readFileSync } from 'fs';\nimport { TetherError } from './errors.js';\nimport type { KeyFormat } from './types.js';\n\n/**\n * Loads a private key from various sources\n */\nexport function loadPrivateKey(options: {\n keyPath?: string;\n keyPem?: string;\n keyBuffer?: Buffer;\n}): KeyObject {\n const { keyPath, keyPem, keyBuffer } = options;\n\n try {\n if (keyPem) {\n // PEM string provided directly\n return createPrivateKey(keyPem);\n }\n \n if (keyBuffer) {\n // DER buffer provided directly\n return createPrivateKey({\n key: keyBuffer,\n format: 'der',\n type: 'pkcs1'\n });\n }\n \n if (keyPath) {\n // Read from file - detect format by extension or content\n const keyData = readFileSync(keyPath);\n \n // Try to detect format\n if (keyPath.endsWith('.pem') || keyData.toString().includes('-----BEGIN')) {\n // PEM format\n return createPrivateKey(keyData);\n } else {\n // Assume DER format\n return createPrivateKey({\n key: keyData,\n format: 'der',\n type: 'pkcs1'\n });\n }\n }\n \n throw new TetherError('No private key provided');\n } catch (error) {\n if (error instanceof TetherError) {\n throw error;\n }\n throw new TetherError(\n `Failed to load private key: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n}\n\n/**\n * Signs a challenge string using RSA-SHA256\n * Returns URL-safe base64 encoded signature (no padding)\n */\nexport function signChallenge(privateKey: KeyObject, challenge: string): string {\n try {\n const sign = createSign('SHA256');\n sign.update(challenge);\n sign.end();\n \n const signature = sign.sign(privateKey);\n \n // Convert to URL-safe base64 without padding\n return signature\n .toString('base64')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '');\n } catch (error) {\n throw new TetherError(\n `Failed to sign challenge: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n}\n\n/**\n * Utility function to detect key format from file extension or content\n */\nexport function detectKeyFormat(keyPath: string): KeyFormat {\n if (keyPath.endsWith('.pem')) {\n return 'pem';\n }\n if (keyPath.endsWith('.der')) {\n return 'der';\n }\n \n // Try to read a small portion to detect format\n try {\n const keyData = readFileSync(keyPath, { encoding: 'utf8', flag: 'r' });\n if (keyData.includes('-----BEGIN')) {\n return 'pem';\n }\n } catch {\n // If we can't read as text, it's probably DER\n }\n \n return 'der';\n}","import { KeyObject } from 'crypto';\nimport { TetherError, TetherAPIError, TetherVerificationError } from './errors.js';\nimport { loadPrivateKey, signChallenge } from './crypto.js';\nimport type {\n TetherClientConfig,\n ChallengeResponse,\n VerificationRequest,\n VerificationResponse,\n VerificationResult\n} from './types.js';\n\n/**\n * TetherClient - Official SDK for tether.name agent identity verification\n */\nexport class TetherClient {\n private readonly credentialId: string;\n private readonly privateKey: KeyObject;\n private readonly baseUrl: string;\n\n constructor(config: TetherClientConfig) {\n // Get credential ID from config or environment\n this.credentialId = config.credentialId || process.env.TETHER_CREDENTIAL_ID || '';\n if (!this.credentialId) {\n throw new TetherError('Credential ID is required. Provide it in config or set TETHER_CREDENTIAL_ID environment variable.');\n }\n\n // Load private key\n const keyPath = config.privateKeyPath || process.env.TETHER_PRIVATE_KEY_PATH;\n this.privateKey = loadPrivateKey({\n keyPath,\n keyPem: config.privateKeyPem,\n keyBuffer: config.privateKeyBuffer\n });\n\n // Set base URL\n this.baseUrl = config.baseUrl || 'https://api.tether.name';\n }\n\n /**\n * Request a challenge from the Tether API\n */\n async requestChallenge(): Promise<string> {\n try {\n const response = await fetch(`${this.baseUrl}/challenge`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => 'Unknown error');\n throw new TetherAPIError(\n `Challenge request failed: ${response.status} ${response.statusText}`,\n response.status,\n errorText\n );\n }\n\n const data = await response.json() as ChallengeResponse;\n \n if (!data.code) {\n throw new TetherAPIError('Invalid challenge response: missing code');\n }\n\n return data.code;\n } catch (error) {\n if (error instanceof TetherError) {\n throw error;\n }\n throw new TetherAPIError(\n `Failed to request challenge: ${error instanceof Error ? error.message : String(error)}`,\n undefined,\n undefined,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Sign a challenge string\n */\n sign(challenge: string): string {\n return signChallenge(this.privateKey, challenge);\n }\n\n /**\n * Submit proof for a challenge\n */\n async submitProof(challenge: string, proof: string): Promise<VerificationResult> {\n try {\n const payload: VerificationRequest = {\n challenge,\n proof,\n credentialId: this.credentialId\n };\n\n const response = await fetch(`${this.baseUrl}/challenge/verify`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(payload)\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => 'Unknown error');\n throw new TetherAPIError(\n `Verification failed: ${response.status} ${response.statusText}`,\n response.status,\n errorText\n );\n }\n\n const data = await response.json() as VerificationResponse;\n\n // Convert API response to our result format\n return {\n verified: data.valid,\n agentName: data.agentName,\n verifyUrl: data.verifyUrl,\n email: data.email,\n registeredSince: data.registeredSince,\n error: data.error,\n challenge\n };\n } catch (error) {\n if (error instanceof TetherError) {\n throw error;\n }\n throw new TetherAPIError(\n `Failed to submit proof: ${error instanceof Error ? error.message : String(error)}`,\n undefined,\n undefined,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Perform complete verification in one call\n */\n async verify(): Promise<VerificationResult> {\n try {\n const challenge = await this.requestChallenge();\n const proof = this.sign(challenge);\n const result = await this.submitProof(challenge, proof);\n\n if (!result.verified) {\n throw new TetherVerificationError(\n result.error || 'Verification failed for unknown reason'\n );\n }\n\n return result;\n } catch (error) {\n if (error instanceof TetherError) {\n throw error;\n }\n throw new TetherVerificationError(\n `Verification failed: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n}"],"mappings":"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","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var TetherError = class _TetherError extends Error {
|
|
3
|
+
constructor(message, cause) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.cause = cause;
|
|
6
|
+
this.name = "TetherError";
|
|
7
|
+
if (Error.captureStackTrace) {
|
|
8
|
+
Error.captureStackTrace(this, _TetherError);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var TetherVerificationError = class extends TetherError {
|
|
13
|
+
constructor(message, cause) {
|
|
14
|
+
super(message, cause);
|
|
15
|
+
this.name = "TetherVerificationError";
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var TetherAPIError = class extends TetherError {
|
|
19
|
+
constructor(message, status, response, cause) {
|
|
20
|
+
super(message, cause);
|
|
21
|
+
this.status = status;
|
|
22
|
+
this.response = response;
|
|
23
|
+
this.name = "TetherAPIError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// src/crypto.ts
|
|
28
|
+
import { createSign, createPrivateKey } from "crypto";
|
|
29
|
+
import { readFileSync } from "fs";
|
|
30
|
+
function loadPrivateKey(options) {
|
|
31
|
+
const { keyPath, keyPem, keyBuffer } = options;
|
|
32
|
+
try {
|
|
33
|
+
if (keyPem) {
|
|
34
|
+
return createPrivateKey(keyPem);
|
|
35
|
+
}
|
|
36
|
+
if (keyBuffer) {
|
|
37
|
+
return createPrivateKey({
|
|
38
|
+
key: keyBuffer,
|
|
39
|
+
format: "der",
|
|
40
|
+
type: "pkcs1"
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (keyPath) {
|
|
44
|
+
const keyData = readFileSync(keyPath);
|
|
45
|
+
if (keyPath.endsWith(".pem") || keyData.toString().includes("-----BEGIN")) {
|
|
46
|
+
return createPrivateKey(keyData);
|
|
47
|
+
} else {
|
|
48
|
+
return createPrivateKey({
|
|
49
|
+
key: keyData,
|
|
50
|
+
format: "der",
|
|
51
|
+
type: "pkcs1"
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
throw new TetherError("No private key provided");
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (error instanceof TetherError) {
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
throw new TetherError(
|
|
61
|
+
`Failed to load private key: ${error instanceof Error ? error.message : String(error)}`,
|
|
62
|
+
error instanceof Error ? error : void 0
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function signChallenge(privateKey, challenge) {
|
|
67
|
+
try {
|
|
68
|
+
const sign = createSign("SHA256");
|
|
69
|
+
sign.update(challenge);
|
|
70
|
+
sign.end();
|
|
71
|
+
const signature = sign.sign(privateKey);
|
|
72
|
+
return signature.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
73
|
+
} catch (error) {
|
|
74
|
+
throw new TetherError(
|
|
75
|
+
`Failed to sign challenge: ${error instanceof Error ? error.message : String(error)}`,
|
|
76
|
+
error instanceof Error ? error : void 0
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function detectKeyFormat(keyPath) {
|
|
81
|
+
if (keyPath.endsWith(".pem")) {
|
|
82
|
+
return "pem";
|
|
83
|
+
}
|
|
84
|
+
if (keyPath.endsWith(".der")) {
|
|
85
|
+
return "der";
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const keyData = readFileSync(keyPath, { encoding: "utf8", flag: "r" });
|
|
89
|
+
if (keyData.includes("-----BEGIN")) {
|
|
90
|
+
return "pem";
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
}
|
|
94
|
+
return "der";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/client.ts
|
|
98
|
+
var TetherClient = class {
|
|
99
|
+
credentialId;
|
|
100
|
+
privateKey;
|
|
101
|
+
baseUrl;
|
|
102
|
+
constructor(config) {
|
|
103
|
+
this.credentialId = config.credentialId || process.env.TETHER_CREDENTIAL_ID || "";
|
|
104
|
+
if (!this.credentialId) {
|
|
105
|
+
throw new TetherError("Credential ID is required. Provide it in config or set TETHER_CREDENTIAL_ID environment variable.");
|
|
106
|
+
}
|
|
107
|
+
const keyPath = config.privateKeyPath || process.env.TETHER_PRIVATE_KEY_PATH;
|
|
108
|
+
this.privateKey = loadPrivateKey({
|
|
109
|
+
keyPath,
|
|
110
|
+
keyPem: config.privateKeyPem,
|
|
111
|
+
keyBuffer: config.privateKeyBuffer
|
|
112
|
+
});
|
|
113
|
+
this.baseUrl = config.baseUrl || "https://api.tether.name";
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Request a challenge from the Tether API
|
|
117
|
+
*/
|
|
118
|
+
async requestChallenge() {
|
|
119
|
+
try {
|
|
120
|
+
const response = await fetch(`${this.baseUrl}/challenge`, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: {
|
|
123
|
+
"Content-Type": "application/json"
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
128
|
+
throw new TetherAPIError(
|
|
129
|
+
`Challenge request failed: ${response.status} ${response.statusText}`,
|
|
130
|
+
response.status,
|
|
131
|
+
errorText
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
const data = await response.json();
|
|
135
|
+
if (!data.code) {
|
|
136
|
+
throw new TetherAPIError("Invalid challenge response: missing code");
|
|
137
|
+
}
|
|
138
|
+
return data.code;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (error instanceof TetherError) {
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
throw new TetherAPIError(
|
|
144
|
+
`Failed to request challenge: ${error instanceof Error ? error.message : String(error)}`,
|
|
145
|
+
void 0,
|
|
146
|
+
void 0,
|
|
147
|
+
error instanceof Error ? error : void 0
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Sign a challenge string
|
|
153
|
+
*/
|
|
154
|
+
sign(challenge) {
|
|
155
|
+
return signChallenge(this.privateKey, challenge);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Submit proof for a challenge
|
|
159
|
+
*/
|
|
160
|
+
async submitProof(challenge, proof) {
|
|
161
|
+
try {
|
|
162
|
+
const payload = {
|
|
163
|
+
challenge,
|
|
164
|
+
proof,
|
|
165
|
+
credentialId: this.credentialId
|
|
166
|
+
};
|
|
167
|
+
const response = await fetch(`${this.baseUrl}/challenge/verify`, {
|
|
168
|
+
method: "POST",
|
|
169
|
+
headers: {
|
|
170
|
+
"Content-Type": "application/json"
|
|
171
|
+
},
|
|
172
|
+
body: JSON.stringify(payload)
|
|
173
|
+
});
|
|
174
|
+
if (!response.ok) {
|
|
175
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
176
|
+
throw new TetherAPIError(
|
|
177
|
+
`Verification failed: ${response.status} ${response.statusText}`,
|
|
178
|
+
response.status,
|
|
179
|
+
errorText
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
const data = await response.json();
|
|
183
|
+
return {
|
|
184
|
+
verified: data.valid,
|
|
185
|
+
agentName: data.agentName,
|
|
186
|
+
verifyUrl: data.verifyUrl,
|
|
187
|
+
email: data.email,
|
|
188
|
+
registeredSince: data.registeredSince,
|
|
189
|
+
error: data.error,
|
|
190
|
+
challenge
|
|
191
|
+
};
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if (error instanceof TetherError) {
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
throw new TetherAPIError(
|
|
197
|
+
`Failed to submit proof: ${error instanceof Error ? error.message : String(error)}`,
|
|
198
|
+
void 0,
|
|
199
|
+
void 0,
|
|
200
|
+
error instanceof Error ? error : void 0
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Perform complete verification in one call
|
|
206
|
+
*/
|
|
207
|
+
async verify() {
|
|
208
|
+
try {
|
|
209
|
+
const challenge = await this.requestChallenge();
|
|
210
|
+
const proof = this.sign(challenge);
|
|
211
|
+
const result = await this.submitProof(challenge, proof);
|
|
212
|
+
if (!result.verified) {
|
|
213
|
+
throw new TetherVerificationError(
|
|
214
|
+
result.error || "Verification failed for unknown reason"
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (error instanceof TetherError) {
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
throw new TetherVerificationError(
|
|
223
|
+
`Verification failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
224
|
+
error instanceof Error ? error : void 0
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
export {
|
|
230
|
+
TetherAPIError,
|
|
231
|
+
TetherClient,
|
|
232
|
+
TetherError,
|
|
233
|
+
TetherVerificationError,
|
|
234
|
+
detectKeyFormat,
|
|
235
|
+
loadPrivateKey,
|
|
236
|
+
signChallenge
|
|
237
|
+
};
|
|
238
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/crypto.ts","../src/client.ts"],"sourcesContent":["/**\n * Base error class for all Tether-related errors\n */\nexport class TetherError extends Error {\n constructor(message: string, public readonly cause?: Error) {\n super(message);\n this.name = 'TetherError';\n \n // Maintain proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, TetherError);\n }\n }\n}\n\n/**\n * Error thrown when verification fails\n */\nexport class TetherVerificationError extends TetherError {\n constructor(message: string, cause?: Error) {\n super(message, cause);\n this.name = 'TetherVerificationError';\n }\n}\n\n/**\n * Error thrown when API requests fail\n */\nexport class TetherAPIError extends TetherError {\n constructor(\n message: string,\n public readonly status?: number,\n public readonly response?: string,\n cause?: Error\n ) {\n super(message, cause);\n this.name = 'TetherAPIError';\n }\n}","import { createSign, createPrivateKey, KeyObject } from 'crypto';\nimport { readFileSync } from 'fs';\nimport { TetherError } from './errors.js';\nimport type { KeyFormat } from './types.js';\n\n/**\n * Loads a private key from various sources\n */\nexport function loadPrivateKey(options: {\n keyPath?: string;\n keyPem?: string;\n keyBuffer?: Buffer;\n}): KeyObject {\n const { keyPath, keyPem, keyBuffer } = options;\n\n try {\n if (keyPem) {\n // PEM string provided directly\n return createPrivateKey(keyPem);\n }\n \n if (keyBuffer) {\n // DER buffer provided directly\n return createPrivateKey({\n key: keyBuffer,\n format: 'der',\n type: 'pkcs1'\n });\n }\n \n if (keyPath) {\n // Read from file - detect format by extension or content\n const keyData = readFileSync(keyPath);\n \n // Try to detect format\n if (keyPath.endsWith('.pem') || keyData.toString().includes('-----BEGIN')) {\n // PEM format\n return createPrivateKey(keyData);\n } else {\n // Assume DER format\n return createPrivateKey({\n key: keyData,\n format: 'der',\n type: 'pkcs1'\n });\n }\n }\n \n throw new TetherError('No private key provided');\n } catch (error) {\n if (error instanceof TetherError) {\n throw error;\n }\n throw new TetherError(\n `Failed to load private key: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n}\n\n/**\n * Signs a challenge string using RSA-SHA256\n * Returns URL-safe base64 encoded signature (no padding)\n */\nexport function signChallenge(privateKey: KeyObject, challenge: string): string {\n try {\n const sign = createSign('SHA256');\n sign.update(challenge);\n sign.end();\n \n const signature = sign.sign(privateKey);\n \n // Convert to URL-safe base64 without padding\n return signature\n .toString('base64')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '');\n } catch (error) {\n throw new TetherError(\n `Failed to sign challenge: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n}\n\n/**\n * Utility function to detect key format from file extension or content\n */\nexport function detectKeyFormat(keyPath: string): KeyFormat {\n if (keyPath.endsWith('.pem')) {\n return 'pem';\n }\n if (keyPath.endsWith('.der')) {\n return 'der';\n }\n \n // Try to read a small portion to detect format\n try {\n const keyData = readFileSync(keyPath, { encoding: 'utf8', flag: 'r' });\n if (keyData.includes('-----BEGIN')) {\n return 'pem';\n }\n } catch {\n // If we can't read as text, it's probably DER\n }\n \n return 'der';\n}","import { KeyObject } from 'crypto';\nimport { TetherError, TetherAPIError, TetherVerificationError } from './errors.js';\nimport { loadPrivateKey, signChallenge } from './crypto.js';\nimport type {\n TetherClientConfig,\n ChallengeResponse,\n VerificationRequest,\n VerificationResponse,\n VerificationResult\n} from './types.js';\n\n/**\n * TetherClient - Official SDK for tether.name agent identity verification\n */\nexport class TetherClient {\n private readonly credentialId: string;\n private readonly privateKey: KeyObject;\n private readonly baseUrl: string;\n\n constructor(config: TetherClientConfig) {\n // Get credential ID from config or environment\n this.credentialId = config.credentialId || process.env.TETHER_CREDENTIAL_ID || '';\n if (!this.credentialId) {\n throw new TetherError('Credential ID is required. Provide it in config or set TETHER_CREDENTIAL_ID environment variable.');\n }\n\n // Load private key\n const keyPath = config.privateKeyPath || process.env.TETHER_PRIVATE_KEY_PATH;\n this.privateKey = loadPrivateKey({\n keyPath,\n keyPem: config.privateKeyPem,\n keyBuffer: config.privateKeyBuffer\n });\n\n // Set base URL\n this.baseUrl = config.baseUrl || 'https://api.tether.name';\n }\n\n /**\n * Request a challenge from the Tether API\n */\n async requestChallenge(): Promise<string> {\n try {\n const response = await fetch(`${this.baseUrl}/challenge`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => 'Unknown error');\n throw new TetherAPIError(\n `Challenge request failed: ${response.status} ${response.statusText}`,\n response.status,\n errorText\n );\n }\n\n const data = await response.json() as ChallengeResponse;\n \n if (!data.code) {\n throw new TetherAPIError('Invalid challenge response: missing code');\n }\n\n return data.code;\n } catch (error) {\n if (error instanceof TetherError) {\n throw error;\n }\n throw new TetherAPIError(\n `Failed to request challenge: ${error instanceof Error ? error.message : String(error)}`,\n undefined,\n undefined,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Sign a challenge string\n */\n sign(challenge: string): string {\n return signChallenge(this.privateKey, challenge);\n }\n\n /**\n * Submit proof for a challenge\n */\n async submitProof(challenge: string, proof: string): Promise<VerificationResult> {\n try {\n const payload: VerificationRequest = {\n challenge,\n proof,\n credentialId: this.credentialId\n };\n\n const response = await fetch(`${this.baseUrl}/challenge/verify`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(payload)\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => 'Unknown error');\n throw new TetherAPIError(\n `Verification failed: ${response.status} ${response.statusText}`,\n response.status,\n errorText\n );\n }\n\n const data = await response.json() as VerificationResponse;\n\n // Convert API response to our result format\n return {\n verified: data.valid,\n agentName: data.agentName,\n verifyUrl: data.verifyUrl,\n email: data.email,\n registeredSince: data.registeredSince,\n error: data.error,\n challenge\n };\n } catch (error) {\n if (error instanceof TetherError) {\n throw error;\n }\n throw new TetherAPIError(\n `Failed to submit proof: ${error instanceof Error ? error.message : String(error)}`,\n undefined,\n undefined,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Perform complete verification in one call\n */\n async verify(): Promise<VerificationResult> {\n try {\n const challenge = await this.requestChallenge();\n const proof = this.sign(challenge);\n const result = await this.submitProof(challenge, proof);\n\n if (!result.verified) {\n throw new TetherVerificationError(\n result.error || 'Verification failed for unknown reason'\n );\n }\n\n return result;\n } catch (error) {\n if (error instanceof TetherError) {\n throw error;\n }\n throw new TetherVerificationError(\n `Verification failed: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n}"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tether-name",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official Node.js SDK for tether.name - AI agent identity verification",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"test": "vitest",
|
|
21
|
+
"test:run": "vitest run",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"tether",
|
|
26
|
+
"agent",
|
|
27
|
+
"verification",
|
|
28
|
+
"identity",
|
|
29
|
+
"crypto",
|
|
30
|
+
"rsa"
|
|
31
|
+
],
|
|
32
|
+
"author": "Commit 451",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"homepage": "https://tether.name",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/tether-name/tether-name-node.git"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/tether-name/tether-name-node/issues"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18.0.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^20.0.0",
|
|
47
|
+
"tsup": "^8.0.0",
|
|
48
|
+
"typescript": "^5.0.0",
|
|
49
|
+
"vitest": "^1.0.0"
|
|
50
|
+
}
|
|
51
|
+
}
|