@tuwaio/siwx-solana 0.3.0 → 0.4.1
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/README.md +48 -45
- package/dist/index.d.mts +78 -30
- package/dist/index.d.ts +78 -30
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +13 -11
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @tuwaio/siwx-solana
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@tuwaio/siwx-solana)
|
|
4
|
-
[](
|
|
4
|
+
[](https://github.com/TuwaIO/siwx/blob/main/packages/siwx-solana/LICENSE)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
`@tuwaio/siwx-solana` is the Solana Layer 2 (L2) package of **SIWX** (Sign-In With X), the authentication project of TUWA Stage 1 ("Core Auth & Primitives", next to Orbit Utils). Built on **`@tuwaio/siwx-core`** and **`@solana/kit`**, it signs CAIP-122 messages with Solana wallets and verifies `solana` ed25519 signatures with the native Web Crypto API. It does not use the legacy `@solana/web3.js` or `gill`.
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
## 🏛️ Core Capabilities
|
|
11
11
|
|
|
12
|
-
- **
|
|
13
|
-
-
|
|
14
|
-
-
|
|
12
|
+
- **Signing:** `createSolanaSiwxSigner` turns a Wallet Standard wallet and account (typed with `@wallet-standard/base`), an `@solana/kit` message signer or a legacy adapter into the `(message) => signature` function that `useSiwx` from [`@tuwaio/siwx-react`](https://siwx.docs.tuwa.io/packages/siwx-react) expects. Signatures are returned as base58 strings.
|
|
13
|
+
- **Verification:** `verifyEd25519` accepts a `{ message, signature }` payload (base58 strings or bytes) or the output of the Wallet Standard `solana:signIn` feature. It requires a `solana` chain and a 64-byte signature, validates the message (format, expiration, `notBefore` and an `issuedAt` in the future) and the address, and checks the signature with `crypto.subtle`. It runs locally, without RPC calls, and returns `{ success, data, error }` instead of throwing.
|
|
14
|
+
- **Runtimes:** any runtime whose Web Crypto API supports Ed25519, such as Node.js 20+ and current browsers. No native modules or polyfills.
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -21,70 +21,73 @@
|
|
|
21
21
|
pnpm add @tuwaio/siwx-solana @tuwaio/siwx-core @solana/kit @wallet-standard/base
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
> [!IMPORTANT]
|
|
25
|
+
> `@tuwaio/siwx-core`, `@solana/kit` (>=8.2) and `@wallet-standard/base` (>=1.1.1) are peer dependencies and must be installed alongside `@tuwaio/siwx-solana`.
|
|
26
|
+
|
|
24
27
|
---
|
|
25
28
|
|
|
26
|
-
## 🚀
|
|
29
|
+
## 🚀 Usage
|
|
27
30
|
|
|
28
|
-
###
|
|
31
|
+
### Signing with a Wallet Standard wallet
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
```typescript
|
|
34
|
+
import { createSolanaSiwxSigner } from '@tuwaio/siwx-solana';
|
|
35
|
+
import type { Wallet, WalletAccount } from '@wallet-standard/base';
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
declare const wallet: Wallet; // a wallet with the `solana:signMessage` feature
|
|
38
|
+
declare const account: WalletAccount; // the connected account, one of `wallet.accounts`
|
|
39
|
+
declare const message: string;
|
|
34
40
|
|
|
35
|
-
const
|
|
36
|
-
message: rawCaip122Message,
|
|
37
|
-
signature: base58EncodedSignature,
|
|
38
|
-
});
|
|
41
|
+
const signer = createSolanaSiwxSigner({ wallet, account });
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
+
// Opens the wallet prompt. Rejects with "[SIWX-SOLANA] Signing failed: …" (original error in `cause`).
|
|
44
|
+
const signature = await signer(message); // base58
|
|
43
45
|
```
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
### `createSolanaSiwxSigner(signer)`
|
|
47
|
+
Pass the Wallet Standard `Wallet` and `WalletAccount` objects. The UI handles of `@wallet-standard/ui` (`UiWallet`, `UiWalletAccount`) only list feature names and cannot sign.
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
### Signing with other signers
|
|
50
50
|
|
|
51
|
-
```
|
|
51
|
+
```typescript
|
|
52
|
+
import type { MessageModifyingSigner } from '@solana/kit';
|
|
52
53
|
import { createSolanaSiwxSigner } from '@tuwaio/siwx-solana';
|
|
53
54
|
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
```
|
|
55
|
+
declare const kitSigner: MessageModifyingSigner; // an @solana/kit message signer
|
|
56
|
+
declare const walletAdapter: { signMessage(message: Uint8Array): Promise<Uint8Array> }; // legacy wallet adapter
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
const signWithKit = createSolanaSiwxSigner(kitSigner);
|
|
59
|
+
const signWithAdapter = createSolanaSiwxSigner(walletAdapter);
|
|
60
|
+
```
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
The signer uses the first capability it finds: `modifyAndSignMessages` (an `@solana/kit` `MessageModifyingSigner`), the `solana:signMessage` feature of `wallet.features`, a `signMessages` method, or a legacy `signMessage` method (also on `adapter`).
|
|
61
63
|
|
|
62
|
-
|
|
64
|
+
### Verifying a signature
|
|
63
65
|
|
|
64
|
-
```
|
|
65
|
-
import {
|
|
66
|
-
import { createSolanaSiwxSigner } from '@tuwaio/siwx-solana';
|
|
66
|
+
```typescript
|
|
67
|
+
import { verifyEd25519 } from '@tuwaio/siwx-solana';
|
|
67
68
|
|
|
68
|
-
const
|
|
69
|
+
declare const message: string;
|
|
70
|
+
declare const signature: string; // base58, as returned by createSolanaSiwxSigner
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
const signer = createSolanaSiwxSigner(connectedAccount);
|
|
72
|
+
const result = await verifyEd25519({ message, signature });
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
if (result.success) {
|
|
75
|
+
console.log(result.data?.address); // "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpK:…"
|
|
76
|
+
} else {
|
|
77
|
+
console.error(result.error);
|
|
78
|
+
}
|
|
74
79
|
```
|
|
75
80
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
## Peer Dependencies
|
|
81
|
+
The output of the Wallet Standard `solana:signIn` feature can be passed as-is (or as `{ output }`); the signed message must be a CAIP-122 message.
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
| ----------------------- | ------------- |
|
|
82
|
-
| `@tuwaio/siwx-core` | `workspace:*` |
|
|
83
|
-
| `@solana/kit` | `>=8.2.0` |
|
|
84
|
-
| `@wallet-standard/base` | `^1.1.1` |
|
|
83
|
+
`verifyEd25519` does not check the domain, URI, nonce or other policy rules. On a server, use [`@tuwaio/siwx-server`](https://siwx.docs.tuwa.io/packages/siwx-server), which adds the policy and single-use nonces, or run `validatePolicy` from `@tuwaio/siwx-core` yourself.
|
|
85
84
|
|
|
86
85
|
---
|
|
87
86
|
|
|
87
|
+
## 📚 API Reference
|
|
88
|
+
|
|
89
|
+
Every export, with signatures and types generated from the source, is documented at **[siwx.docs.tuwa.io/packages/siwx-solana](https://siwx.docs.tuwa.io/packages/siwx-solana)**.
|
|
90
|
+
|
|
88
91
|
## 📄 License
|
|
89
92
|
|
|
90
|
-
Licensed under the **Apache-2.0 License**. See the [LICENSE](
|
|
93
|
+
Licensed under the **Apache-2.0 License**. See the [LICENSE](https://github.com/TuwaIO/siwx/blob/main/packages/siwx-solana/LICENSE) file for details.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,51 +1,96 @@
|
|
|
1
|
+
import { MessageModifyingSigner } from '@solana/kit';
|
|
2
|
+
import { Wallet, WalletAccount } from '@wallet-standard/base';
|
|
1
3
|
import { SiwxVerifyPayload, SiwxVerifyResult } from '@tuwaio/siwx-core';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
|
-
*
|
|
5
|
-
* Accepts raw wallet and account objects, or a unified signer object.
|
|
6
|
+
* @file Solana signer adapter for SIWX authentication.
|
|
6
7
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A Wallet Standard wallet and the account to sign with. The wallet must provide the `solana:signMessage` feature.
|
|
11
|
+
*/
|
|
12
|
+
interface SolanaWalletStandardSignerTarget {
|
|
13
|
+
/** The Wallet Standard wallet. */
|
|
14
|
+
wallet: Wallet;
|
|
15
|
+
/** The connected account, one of `wallet.accounts`. Its `address` identifies the signature. */
|
|
16
|
+
account: WalletAccount;
|
|
11
17
|
}
|
|
12
18
|
/**
|
|
13
|
-
*
|
|
14
|
-
|
|
19
|
+
* A legacy wallet adapter (for example from `@solana/wallet-adapter-react`) that signs raw message bytes.
|
|
20
|
+
*/
|
|
21
|
+
interface SolanaLegacyMessageSigner {
|
|
22
|
+
/**
|
|
23
|
+
* Signs the message bytes.
|
|
24
|
+
* @param message - UTF-8 bytes of the message.
|
|
25
|
+
* @returns The 64-byte ed25519 signature, or an object that contains it.
|
|
26
|
+
*/
|
|
27
|
+
signMessage(message: Uint8Array): Promise<Uint8Array | {
|
|
28
|
+
signature: Uint8Array;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* What {@link createSolanaSiwxSigner} can sign with:
|
|
33
|
+
*
|
|
34
|
+
* - a Wallet Standard `{ wallet, account }` pair ({@link SolanaWalletStandardSignerTarget});
|
|
35
|
+
* - an `@solana/kit` `MessageModifyingSigner`;
|
|
36
|
+
* - a legacy adapter with `signMessage(bytes)` ({@link SolanaLegacyMessageSigner});
|
|
37
|
+
* - any `{ wallet, account }` objects with one of these capabilities, such as a wallet exposing an `adapter` or a
|
|
38
|
+
* `signMessages` method.
|
|
15
39
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
40
|
+
* The signer uses the first capability it finds: `modifyAndSignMessages`, the `solana:signMessage` feature of
|
|
41
|
+
* `wallet.features`, a `signMessages` method, or a `signMessage` method (also looked up on `adapter`).
|
|
42
|
+
*/
|
|
43
|
+
type SolanaSiwxSignerTarget = SolanaWalletStandardSignerTarget | MessageModifyingSigner | SolanaLegacyMessageSigner | {
|
|
44
|
+
wallet?: object;
|
|
45
|
+
account?: object;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Creates a SIWX signer for Solana wallets: a function that signs a message (UTF-8 bytes) with the wallet and returns
|
|
49
|
+
* the ed25519 signature as a base58 string. Pass it as `signer` to `useSiwx().signIn` from `@tuwaio/siwx-react`.
|
|
50
|
+
*
|
|
51
|
+
* Works with Wallet Standard wallets, `@solana/kit` message signers and legacy adapters; see
|
|
52
|
+
* {@link SolanaSiwxSignerTarget} for how the signing method is chosen.
|
|
53
|
+
*
|
|
54
|
+
* @param target - The Wallet Standard wallet and account, an `@solana/kit` message signer or a legacy adapter.
|
|
55
|
+
* @returns An async signer. Calling it opens the wallet signature prompt; it rejects with an `Error` whose message
|
|
56
|
+
* starts with `[SIWX-SOLANA] Signing failed:` (original error in `cause`) when the target has no signing
|
|
57
|
+
* capability, the wallet rejects, or no signature is returned for the account address.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* const signer = createSolanaSiwxSigner({ wallet, account });
|
|
62
|
+
* const signature = await signer(message);
|
|
63
|
+
* ```
|
|
18
64
|
*/
|
|
19
65
|
declare function createSolanaSiwxSigner(target: SolanaSiwxSignerTarget): (message: string) => Promise<string>;
|
|
20
66
|
|
|
21
67
|
/**
|
|
22
|
-
* @
|
|
68
|
+
* @file Solana-specific types for the @tuwaio/siwx-solana package.
|
|
23
69
|
*/
|
|
24
70
|
|
|
25
71
|
/**
|
|
26
|
-
* Minimal
|
|
72
|
+
* Minimal account shape of a Wallet Standard `solana:signIn` output.
|
|
27
73
|
*/
|
|
28
74
|
interface SolanaSignInAccount {
|
|
29
|
-
/** Base58
|
|
75
|
+
/** Base58-encoded wallet address. Not used for verification: the signer is taken from the message `address`. */
|
|
30
76
|
address: string;
|
|
31
|
-
/** Raw public key bytes. */
|
|
77
|
+
/** Raw public key bytes. Not used for verification. */
|
|
32
78
|
publicKey?: Uint8Array;
|
|
33
79
|
}
|
|
34
80
|
/**
|
|
35
|
-
* Output
|
|
81
|
+
* Output of the Wallet Standard `solana:signIn` feature, accepted as-is by {@link verifyEd25519}.
|
|
36
82
|
*/
|
|
37
83
|
interface SolanaSignInOutput {
|
|
38
84
|
/** Account that signed the message. */
|
|
39
85
|
account: SolanaSignInAccount;
|
|
40
|
-
/**
|
|
86
|
+
/** The signed message, as UTF-8 bytes or a string. It must be a CAIP-122 message. */
|
|
41
87
|
signedMessage: Uint8Array | string;
|
|
42
|
-
/**
|
|
88
|
+
/** The ed25519 signature, as 64 raw bytes or a base58 string. */
|
|
43
89
|
signature: Uint8Array | string;
|
|
44
90
|
}
|
|
45
91
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* raw Uint8Array buffers, or Wallet Standard `solana:signIn` output objects.
|
|
92
|
+
* Input of {@link verifyEd25519}: a `{ message, signature }` payload (strings or bytes), a Wallet Standard
|
|
93
|
+
* `solana:signIn` output, or that output wrapped as `{ output }`. String signatures are base58-encoded.
|
|
49
94
|
*/
|
|
50
95
|
type SolanaVerifyPayload = SiwxVerifyPayload | {
|
|
51
96
|
message: string | Uint8Array;
|
|
@@ -55,21 +100,24 @@ type SolanaVerifyPayload = SiwxVerifyPayload | {
|
|
|
55
100
|
};
|
|
56
101
|
|
|
57
102
|
/**
|
|
58
|
-
* @
|
|
103
|
+
* @file Solana signature verification for CAIP-122 messages.
|
|
59
104
|
* Uses ed25519 cryptography via the native SubtleCrypto API (Node.js & browser compatible).
|
|
60
105
|
*/
|
|
61
106
|
|
|
62
107
|
/**
|
|
63
|
-
* Verifies a
|
|
64
|
-
*
|
|
65
|
-
*
|
|
108
|
+
* Verifies a `solana` CAIP-122 message signed with ed25519.
|
|
109
|
+
*
|
|
110
|
+
* Parses the message, requires a `solana` chain and a 64-byte signature, runs `validateMessage` from `@tuwaio/siwx-core` (format,
|
|
111
|
+
* expiration, `notBefore` and an `issuedAt` in the future), validates the message `address` with `@solana/kit` and checks the signature against
|
|
112
|
+
* that public key with Web Crypto (`crypto.subtle`, algorithm `Ed25519`). Runs locally, without RPC calls. Nonce,
|
|
113
|
+
* domain and policy checks are the caller's job (see `@tuwaio/siwx-server`).
|
|
66
114
|
*
|
|
67
|
-
*
|
|
68
|
-
* compatibility with both Node.js (v19+) and browser environments without polyfills.
|
|
115
|
+
* Requires a runtime with Ed25519 support in Web Crypto (current browsers, Node.js 20+, edge runtimes).
|
|
69
116
|
*
|
|
70
|
-
* @param payload -
|
|
71
|
-
* @param options -
|
|
72
|
-
* @returns
|
|
117
|
+
* @param payload - `{ message, signature }` (base58 string or bytes), or a Wallet Standard `solana:signIn` output.
|
|
118
|
+
* @param options - `skipExpiration` skips the check that `expirationTime` has not passed.
|
|
119
|
+
* @returns `{ success: true, data }`, or `{ success: false, error }`. Never throws: decoding, parse, validation and
|
|
120
|
+
* crypto errors are returned as `error`.
|
|
73
121
|
*
|
|
74
122
|
* @example
|
|
75
123
|
* ```ts
|
|
@@ -81,4 +129,4 @@ declare function verifyEd25519(payload: SolanaVerifyPayload, options?: {
|
|
|
81
129
|
skipExpiration?: boolean;
|
|
82
130
|
}): Promise<SiwxVerifyResult>;
|
|
83
131
|
|
|
84
|
-
export { type SolanaSignInAccount, type SolanaSignInOutput, type SolanaSiwxSignerTarget, type SolanaVerifyPayload, createSolanaSiwxSigner, verifyEd25519 };
|
|
132
|
+
export { type SolanaLegacyMessageSigner, type SolanaSignInAccount, type SolanaSignInOutput, type SolanaSiwxSignerTarget, type SolanaVerifyPayload, type SolanaWalletStandardSignerTarget, createSolanaSiwxSigner, verifyEd25519 };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,51 +1,96 @@
|
|
|
1
|
+
import { MessageModifyingSigner } from '@solana/kit';
|
|
2
|
+
import { Wallet, WalletAccount } from '@wallet-standard/base';
|
|
1
3
|
import { SiwxVerifyPayload, SiwxVerifyResult } from '@tuwaio/siwx-core';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
|
-
*
|
|
5
|
-
* Accepts raw wallet and account objects, or a unified signer object.
|
|
6
|
+
* @file Solana signer adapter for SIWX authentication.
|
|
6
7
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A Wallet Standard wallet and the account to sign with. The wallet must provide the `solana:signMessage` feature.
|
|
11
|
+
*/
|
|
12
|
+
interface SolanaWalletStandardSignerTarget {
|
|
13
|
+
/** The Wallet Standard wallet. */
|
|
14
|
+
wallet: Wallet;
|
|
15
|
+
/** The connected account, one of `wallet.accounts`. Its `address` identifies the signature. */
|
|
16
|
+
account: WalletAccount;
|
|
11
17
|
}
|
|
12
18
|
/**
|
|
13
|
-
*
|
|
14
|
-
|
|
19
|
+
* A legacy wallet adapter (for example from `@solana/wallet-adapter-react`) that signs raw message bytes.
|
|
20
|
+
*/
|
|
21
|
+
interface SolanaLegacyMessageSigner {
|
|
22
|
+
/**
|
|
23
|
+
* Signs the message bytes.
|
|
24
|
+
* @param message - UTF-8 bytes of the message.
|
|
25
|
+
* @returns The 64-byte ed25519 signature, or an object that contains it.
|
|
26
|
+
*/
|
|
27
|
+
signMessage(message: Uint8Array): Promise<Uint8Array | {
|
|
28
|
+
signature: Uint8Array;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* What {@link createSolanaSiwxSigner} can sign with:
|
|
33
|
+
*
|
|
34
|
+
* - a Wallet Standard `{ wallet, account }` pair ({@link SolanaWalletStandardSignerTarget});
|
|
35
|
+
* - an `@solana/kit` `MessageModifyingSigner`;
|
|
36
|
+
* - a legacy adapter with `signMessage(bytes)` ({@link SolanaLegacyMessageSigner});
|
|
37
|
+
* - any `{ wallet, account }` objects with one of these capabilities, such as a wallet exposing an `adapter` or a
|
|
38
|
+
* `signMessages` method.
|
|
15
39
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
40
|
+
* The signer uses the first capability it finds: `modifyAndSignMessages`, the `solana:signMessage` feature of
|
|
41
|
+
* `wallet.features`, a `signMessages` method, or a `signMessage` method (also looked up on `adapter`).
|
|
42
|
+
*/
|
|
43
|
+
type SolanaSiwxSignerTarget = SolanaWalletStandardSignerTarget | MessageModifyingSigner | SolanaLegacyMessageSigner | {
|
|
44
|
+
wallet?: object;
|
|
45
|
+
account?: object;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Creates a SIWX signer for Solana wallets: a function that signs a message (UTF-8 bytes) with the wallet and returns
|
|
49
|
+
* the ed25519 signature as a base58 string. Pass it as `signer` to `useSiwx().signIn` from `@tuwaio/siwx-react`.
|
|
50
|
+
*
|
|
51
|
+
* Works with Wallet Standard wallets, `@solana/kit` message signers and legacy adapters; see
|
|
52
|
+
* {@link SolanaSiwxSignerTarget} for how the signing method is chosen.
|
|
53
|
+
*
|
|
54
|
+
* @param target - The Wallet Standard wallet and account, an `@solana/kit` message signer or a legacy adapter.
|
|
55
|
+
* @returns An async signer. Calling it opens the wallet signature prompt; it rejects with an `Error` whose message
|
|
56
|
+
* starts with `[SIWX-SOLANA] Signing failed:` (original error in `cause`) when the target has no signing
|
|
57
|
+
* capability, the wallet rejects, or no signature is returned for the account address.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* const signer = createSolanaSiwxSigner({ wallet, account });
|
|
62
|
+
* const signature = await signer(message);
|
|
63
|
+
* ```
|
|
18
64
|
*/
|
|
19
65
|
declare function createSolanaSiwxSigner(target: SolanaSiwxSignerTarget): (message: string) => Promise<string>;
|
|
20
66
|
|
|
21
67
|
/**
|
|
22
|
-
* @
|
|
68
|
+
* @file Solana-specific types for the @tuwaio/siwx-solana package.
|
|
23
69
|
*/
|
|
24
70
|
|
|
25
71
|
/**
|
|
26
|
-
* Minimal
|
|
72
|
+
* Minimal account shape of a Wallet Standard `solana:signIn` output.
|
|
27
73
|
*/
|
|
28
74
|
interface SolanaSignInAccount {
|
|
29
|
-
/** Base58
|
|
75
|
+
/** Base58-encoded wallet address. Not used for verification: the signer is taken from the message `address`. */
|
|
30
76
|
address: string;
|
|
31
|
-
/** Raw public key bytes. */
|
|
77
|
+
/** Raw public key bytes. Not used for verification. */
|
|
32
78
|
publicKey?: Uint8Array;
|
|
33
79
|
}
|
|
34
80
|
/**
|
|
35
|
-
* Output
|
|
81
|
+
* Output of the Wallet Standard `solana:signIn` feature, accepted as-is by {@link verifyEd25519}.
|
|
36
82
|
*/
|
|
37
83
|
interface SolanaSignInOutput {
|
|
38
84
|
/** Account that signed the message. */
|
|
39
85
|
account: SolanaSignInAccount;
|
|
40
|
-
/**
|
|
86
|
+
/** The signed message, as UTF-8 bytes or a string. It must be a CAIP-122 message. */
|
|
41
87
|
signedMessage: Uint8Array | string;
|
|
42
|
-
/**
|
|
88
|
+
/** The ed25519 signature, as 64 raw bytes or a base58 string. */
|
|
43
89
|
signature: Uint8Array | string;
|
|
44
90
|
}
|
|
45
91
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* raw Uint8Array buffers, or Wallet Standard `solana:signIn` output objects.
|
|
92
|
+
* Input of {@link verifyEd25519}: a `{ message, signature }` payload (strings or bytes), a Wallet Standard
|
|
93
|
+
* `solana:signIn` output, or that output wrapped as `{ output }`. String signatures are base58-encoded.
|
|
49
94
|
*/
|
|
50
95
|
type SolanaVerifyPayload = SiwxVerifyPayload | {
|
|
51
96
|
message: string | Uint8Array;
|
|
@@ -55,21 +100,24 @@ type SolanaVerifyPayload = SiwxVerifyPayload | {
|
|
|
55
100
|
};
|
|
56
101
|
|
|
57
102
|
/**
|
|
58
|
-
* @
|
|
103
|
+
* @file Solana signature verification for CAIP-122 messages.
|
|
59
104
|
* Uses ed25519 cryptography via the native SubtleCrypto API (Node.js & browser compatible).
|
|
60
105
|
*/
|
|
61
106
|
|
|
62
107
|
/**
|
|
63
|
-
* Verifies a
|
|
64
|
-
*
|
|
65
|
-
*
|
|
108
|
+
* Verifies a `solana` CAIP-122 message signed with ed25519.
|
|
109
|
+
*
|
|
110
|
+
* Parses the message, requires a `solana` chain and a 64-byte signature, runs `validateMessage` from `@tuwaio/siwx-core` (format,
|
|
111
|
+
* expiration, `notBefore` and an `issuedAt` in the future), validates the message `address` with `@solana/kit` and checks the signature against
|
|
112
|
+
* that public key with Web Crypto (`crypto.subtle`, algorithm `Ed25519`). Runs locally, without RPC calls. Nonce,
|
|
113
|
+
* domain and policy checks are the caller's job (see `@tuwaio/siwx-server`).
|
|
66
114
|
*
|
|
67
|
-
*
|
|
68
|
-
* compatibility with both Node.js (v19+) and browser environments without polyfills.
|
|
115
|
+
* Requires a runtime with Ed25519 support in Web Crypto (current browsers, Node.js 20+, edge runtimes).
|
|
69
116
|
*
|
|
70
|
-
* @param payload -
|
|
71
|
-
* @param options -
|
|
72
|
-
* @returns
|
|
117
|
+
* @param payload - `{ message, signature }` (base58 string or bytes), or a Wallet Standard `solana:signIn` output.
|
|
118
|
+
* @param options - `skipExpiration` skips the check that `expirationTime` has not passed.
|
|
119
|
+
* @returns `{ success: true, data }`, or `{ success: false, error }`. Never throws: decoding, parse, validation and
|
|
120
|
+
* crypto errors are returned as `error`.
|
|
73
121
|
*
|
|
74
122
|
* @example
|
|
75
123
|
* ```ts
|
|
@@ -81,4 +129,4 @@ declare function verifyEd25519(payload: SolanaVerifyPayload, options?: {
|
|
|
81
129
|
skipExpiration?: boolean;
|
|
82
130
|
}): Promise<SiwxVerifyResult>;
|
|
83
131
|
|
|
84
|
-
export { type SolanaSignInAccount, type SolanaSignInOutput, type SolanaSiwxSignerTarget, type SolanaVerifyPayload, createSolanaSiwxSigner, verifyEd25519 };
|
|
132
|
+
export { type SolanaLegacyMessageSigner, type SolanaSignInAccount, type SolanaSignInOutput, type SolanaSiwxSignerTarget, type SolanaVerifyPayload, type SolanaWalletStandardSignerTarget, createSolanaSiwxSigner, verifyEd25519 };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';var kit=require('@solana/kit'),siwxCore=require('@tuwaio/siwx-core');function
|
|
1
|
+
'use strict';var kit=require('@solana/kit'),siwxCore=require('@tuwaio/siwx-core');function W(e,n){if(e===n)return true;if(e.length!==n.length)return false;for(let s=0;s<e.length;s++)if(e[s]!==n[s])return false;return true}function v(e,n){if(typeof e?.modifyAndSignMessages=="function")return e;if(typeof n?.modifyAndSignMessages=="function")return n;let s=n?.features,t=e?.features,r;Array.isArray(s)&&s.includes("solana:signMessage"),t&&typeof t=="object"&&!Array.isArray(t)&&(r=t["solana:signMessage"]);let i=e?.adapter??n?.adapter,o=e?.signMessage??i?.signMessage??n?.signMessage,l=e?.signMessages??n?.signMessages;if(!r&&!o&&!l)throw new Error("[SIWX-SOLANA] Signer lacks known message signing capabilities.");let a=String(n?.address??e?.address??"solana:signer");return {address:a,async modifyAndSignMessages(g,p={}){if(p.abortSignal?.aborted)throw new Error("Aborted");if(g.length===0)return g;let y=[];for(let m=0;m<g.length;m++){let d=g[m],c,S;if(r){let u=[{account:n,message:d.content}],A=(await r.signMessage(...u))[0];if(!A||!A.signature)throw new Error("[SIWX-SOLANA] Wallet returned invalid signMessage output.");c=A.signature,S=A.signedMessage??d.content;}else if(typeof l=="function"){let f=(await l([{account:n,message:d.content}]))[0];if(!f||!f.signature)throw new Error("[SIWX-SOLANA] Wallet returned invalid signMessages output.");c=f.signature,S=f.signedMessage??d.content;}else if(typeof o=="function"){let u=await o.call(i??e??n,d.content);if(S=d.content,u instanceof Uint8Array||u&&u.buffer instanceof ArrayBuffer)c=u;else if(u&&typeof u=="object"&&"signature"in u)c=u.signature;else throw new Error("[SIWX-SOLANA] Unexpected legacy signMessage result format.")}else throw new Error("[SIWX-SOLANA] Missing signing implementation.");let h=d.content.length!==S.length||d.content.some((u,f)=>u!==S[f]),b=d.signatures[a];if(!(b===void 0||!W(b,c))&&!h){y.push(d);continue}let U=h?{[a]:c}:{...d.signatures,[a]:c};y.push(Object.freeze({content:S,signatures:Object.freeze(U)}));}return y}}}function P(e){return async n=>{try{if(!e)throw new Error("[SIWX-SOLANA] Invalid signer target.");let{wallet:s,account:t}=e,r=s??e,i=t??e,l=kit.getUtf8Encoder().encode(n),a=v(r,i),g=kit.createSignableMessage(l),M=(await a.modifyAndSignMessages([g]))[0];if(!M)throw new Error("[SIWX-SOLANA] No signed message returned.");let y=M.signatures[a.address];if(!y)throw new Error(`[SIWX-SOLANA] Signature missing for address: ${a.address}`);return kit.getBase58Decoder().decode(y)}catch(s){throw new Error(`[SIWX-SOLANA] Signing failed: ${s.message}`,{cause:s})}}}function x(e){let n="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",s=new Map(n.split("").map((a,g)=>[a,BigInt(g)])),t=0n;for(let a of e){let g=s.get(a);if(g===void 0)throw new siwxCore.SiwxVerificationError(`Invalid base58 character: "${a}"`);t=t*58n+g;}let r=t.toString(16);r.length%2!==0&&(r="0"+r);let i=[];for(let a=0;a<r.length;a+=2)i.push(parseInt(r.slice(a,a+2),16));let o=0;for(let a of e)if(a==="1")o++;else break;let l=new Uint8Array(o+i.length);return l.set(i,o),l}function V(e){let n=e.split(":");if(n.length!==3||n[0]!=="solana")throw new siwxCore.SiwxVerificationError(`Expected solana CAIP-10 address format. Got: "${e}"`);return n[2]}function j(e){let n="output"in e?e.output:e,s="signedMessage"in n?n.signedMessage:n.message,t=n.signature,r,i;s instanceof Uint8Array?(i=s,r=new TextDecoder().decode(s)):(r=s,i=new TextEncoder().encode(s));let o;return t instanceof Uint8Array?o=t:o=x(t),{messageString:r,messageBytes:i,signatureBytes:o}}async function R(e,n){try{let{messageString:s,messageBytes:t,signatureBytes:r}=j(e),i=siwxCore.parseMessage(s);if(!i.chainId.startsWith("solana:"))throw new siwxCore.SiwxUnsupportedNamespaceError(i.chainId.split(":")[0]??"unknown");if(r.length!==64)throw new siwxCore.SiwxVerificationError(`Invalid Solana signature length: ${r.length} bytes (expected 64)`);let o=siwxCore.validateMessage(i,{skipExpiration:n?.skipExpiration,policy:{enforceNotBefore:!0}});if(!o.valid)throw new siwxCore.SiwxValidationError(o.errors);let l=V(i.address),a=kit.address(l),g=x(a),p=await globalThis.crypto.subtle.importKey("raw",g.buffer,{name:"Ed25519"},!1,["verify"]);if(!await globalThis.crypto.subtle.verify({name:"Ed25519"},p,r.buffer,t.buffer))throw new siwxCore.SiwxVerificationError(`ed25519 signature verification failed for address: ${l}`);return {success:!0,data:i}}catch(s){return s instanceof siwxCore.SiwxVerificationError||s instanceof siwxCore.SiwxValidationError?{success:false,error:s.message}:{success:false,error:`ed25519 verification failed: ${String(s)}`}}}exports.createSolanaSiwxSigner=P;exports.verifyEd25519=R;
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import {getUtf8Encoder,createSignableMessage,getBase58Decoder,address}from'@solana/kit';import {parseMessage,SiwxUnsupportedNamespaceError,SiwxVerificationError,validateMessage,SiwxValidationError}from'@tuwaio/siwx-core';function
|
|
1
|
+
import {getUtf8Encoder,createSignableMessage,getBase58Decoder,address}from'@solana/kit';import {parseMessage,SiwxUnsupportedNamespaceError,SiwxVerificationError,validateMessage,SiwxValidationError}from'@tuwaio/siwx-core';function W(e,n){if(e===n)return true;if(e.length!==n.length)return false;for(let s=0;s<e.length;s++)if(e[s]!==n[s])return false;return true}function v(e,n){if(typeof e?.modifyAndSignMessages=="function")return e;if(typeof n?.modifyAndSignMessages=="function")return n;let s=n?.features,t=e?.features,r;Array.isArray(s)&&s.includes("solana:signMessage"),t&&typeof t=="object"&&!Array.isArray(t)&&(r=t["solana:signMessage"]);let i=e?.adapter??n?.adapter,o=e?.signMessage??i?.signMessage??n?.signMessage,l=e?.signMessages??n?.signMessages;if(!r&&!o&&!l)throw new Error("[SIWX-SOLANA] Signer lacks known message signing capabilities.");let a=String(n?.address??e?.address??"solana:signer");return {address:a,async modifyAndSignMessages(g,p={}){if(p.abortSignal?.aborted)throw new Error("Aborted");if(g.length===0)return g;let y=[];for(let m=0;m<g.length;m++){let d=g[m],c,S;if(r){let u=[{account:n,message:d.content}],A=(await r.signMessage(...u))[0];if(!A||!A.signature)throw new Error("[SIWX-SOLANA] Wallet returned invalid signMessage output.");c=A.signature,S=A.signedMessage??d.content;}else if(typeof l=="function"){let f=(await l([{account:n,message:d.content}]))[0];if(!f||!f.signature)throw new Error("[SIWX-SOLANA] Wallet returned invalid signMessages output.");c=f.signature,S=f.signedMessage??d.content;}else if(typeof o=="function"){let u=await o.call(i??e??n,d.content);if(S=d.content,u instanceof Uint8Array||u&&u.buffer instanceof ArrayBuffer)c=u;else if(u&&typeof u=="object"&&"signature"in u)c=u.signature;else throw new Error("[SIWX-SOLANA] Unexpected legacy signMessage result format.")}else throw new Error("[SIWX-SOLANA] Missing signing implementation.");let h=d.content.length!==S.length||d.content.some((u,f)=>u!==S[f]),b=d.signatures[a];if(!(b===void 0||!W(b,c))&&!h){y.push(d);continue}let U=h?{[a]:c}:{...d.signatures,[a]:c};y.push(Object.freeze({content:S,signatures:Object.freeze(U)}));}return y}}}function P(e){return async n=>{try{if(!e)throw new Error("[SIWX-SOLANA] Invalid signer target.");let{wallet:s,account:t}=e,r=s??e,i=t??e,l=getUtf8Encoder().encode(n),a=v(r,i),g=createSignableMessage(l),M=(await a.modifyAndSignMessages([g]))[0];if(!M)throw new Error("[SIWX-SOLANA] No signed message returned.");let y=M.signatures[a.address];if(!y)throw new Error(`[SIWX-SOLANA] Signature missing for address: ${a.address}`);return getBase58Decoder().decode(y)}catch(s){throw new Error(`[SIWX-SOLANA] Signing failed: ${s.message}`,{cause:s})}}}function x(e){let n="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",s=new Map(n.split("").map((a,g)=>[a,BigInt(g)])),t=0n;for(let a of e){let g=s.get(a);if(g===void 0)throw new SiwxVerificationError(`Invalid base58 character: "${a}"`);t=t*58n+g;}let r=t.toString(16);r.length%2!==0&&(r="0"+r);let i=[];for(let a=0;a<r.length;a+=2)i.push(parseInt(r.slice(a,a+2),16));let o=0;for(let a of e)if(a==="1")o++;else break;let l=new Uint8Array(o+i.length);return l.set(i,o),l}function V(e){let n=e.split(":");if(n.length!==3||n[0]!=="solana")throw new SiwxVerificationError(`Expected solana CAIP-10 address format. Got: "${e}"`);return n[2]}function j(e){let n="output"in e?e.output:e,s="signedMessage"in n?n.signedMessage:n.message,t=n.signature,r,i;s instanceof Uint8Array?(i=s,r=new TextDecoder().decode(s)):(r=s,i=new TextEncoder().encode(s));let o;return t instanceof Uint8Array?o=t:o=x(t),{messageString:r,messageBytes:i,signatureBytes:o}}async function R(e,n){try{let{messageString:s,messageBytes:t,signatureBytes:r}=j(e),i=parseMessage(s);if(!i.chainId.startsWith("solana:"))throw new SiwxUnsupportedNamespaceError(i.chainId.split(":")[0]??"unknown");if(r.length!==64)throw new SiwxVerificationError(`Invalid Solana signature length: ${r.length} bytes (expected 64)`);let o=validateMessage(i,{skipExpiration:n?.skipExpiration,policy:{enforceNotBefore:!0}});if(!o.valid)throw new SiwxValidationError(o.errors);let l=V(i.address),a=address(l),g=x(a),p=await globalThis.crypto.subtle.importKey("raw",g.buffer,{name:"Ed25519"},!1,["verify"]);if(!await globalThis.crypto.subtle.verify({name:"Ed25519"},p,r.buffer,t.buffer))throw new SiwxVerificationError(`ed25519 signature verification failed for address: ${l}`);return {success:!0,data:i}}catch(s){return s instanceof SiwxVerificationError||s instanceof SiwxValidationError?{success:false,error:s.message}:{success:false,error:`ed25519 verification failed: ${String(s)}`}}}export{P as createSolanaSiwxSigner,R as verifyEd25519};
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuwaio/siwx-solana",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Oleksandr Tkach",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
|
-
"description": "
|
|
7
|
+
"description": "L2 Solana package of SIWX (TUWA): CAIP-122 signer for Wallet Standard and @solana/kit wallets, and ed25519 signature verification with Web Crypto, built on @solana/kit.",
|
|
8
8
|
"main": "./dist/index.js",
|
|
9
9
|
"module": "./dist/index.mjs",
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
@@ -16,16 +16,18 @@
|
|
|
16
16
|
"socket.json"
|
|
17
17
|
],
|
|
18
18
|
"keywords": [
|
|
19
|
-
"
|
|
20
|
-
"
|
|
19
|
+
"tuwa",
|
|
20
|
+
"siwx",
|
|
21
|
+
"sign-in-with-x",
|
|
21
22
|
"caip-122",
|
|
23
|
+
"authentication",
|
|
24
|
+
"web3",
|
|
22
25
|
"solana",
|
|
23
26
|
"ed25519",
|
|
24
27
|
"@solana/kit",
|
|
25
28
|
"wallet-standard",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"tuwa"
|
|
29
|
+
"typescript",
|
|
30
|
+
"headless"
|
|
29
31
|
],
|
|
30
32
|
"repository": {
|
|
31
33
|
"type": "git",
|
|
@@ -43,16 +45,16 @@
|
|
|
43
45
|
}
|
|
44
46
|
],
|
|
45
47
|
"peerDependencies": {
|
|
46
|
-
"@tuwaio/siwx-core": "0.
|
|
48
|
+
"@tuwaio/siwx-core": "0.4.0",
|
|
47
49
|
"@wallet-standard/base": ">=1.1.1",
|
|
48
50
|
"@solana/kit": ">=8.2"
|
|
49
51
|
},
|
|
50
52
|
"devDependencies": {
|
|
51
|
-
"@tuwaio/siwx-core": "0.
|
|
53
|
+
"@tuwaio/siwx-core": "0.4.0",
|
|
52
54
|
"@wallet-standard/base": "^1.1.1",
|
|
53
|
-
"@solana/kit": "^8.
|
|
55
|
+
"@solana/kit": "^8.4.0",
|
|
54
56
|
"tsup": "^8.5.1",
|
|
55
|
-
"typescript": "
|
|
57
|
+
"typescript": "6.0.3"
|
|
56
58
|
},
|
|
57
59
|
"scripts": {
|
|
58
60
|
"start": "tsup src/index.ts --watch",
|