passkey-kit 0.12.0 → 0.13.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 +399 -227
- package/{types → dist}/base.d.ts +2 -1
- package/dist/base.d.ts.map +1 -0
- package/{src/base.ts → dist/base.js} +8 -10
- package/dist/base.js.map +1 -0
- package/dist/base64url.d.ts +18 -0
- package/dist/base64url.d.ts.map +1 -0
- package/dist/base64url.js +18 -0
- package/dist/base64url.js.map +1 -0
- package/dist/constants.d.ts +87 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +111 -0
- package/dist/constants.js.map +1 -0
- package/dist/contract-errors.d.ts +69 -0
- package/dist/contract-errors.d.ts.map +1 -0
- package/dist/contract-errors.js +129 -0
- package/dist/contract-errors.js.map +1 -0
- package/dist/errors.d.ts +163 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +244 -0
- package/dist/errors.js.map +1 -0
- package/dist/events.d.ts +94 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +92 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer/codec.d.ts +18 -0
- package/dist/indexer/codec.d.ts.map +1 -0
- package/dist/indexer/codec.js +32 -0
- package/dist/indexer/codec.js.map +1 -0
- package/dist/indexer/index.d.ts +25 -0
- package/dist/indexer/index.d.ts.map +1 -0
- package/dist/indexer/index.js +34 -0
- package/dist/indexer/index.js.map +1 -0
- package/dist/indexer/mercury.d.ts +88 -0
- package/dist/indexer/mercury.d.ts.map +1 -0
- package/dist/indexer/mercury.js +237 -0
- package/dist/indexer/mercury.js.map +1 -0
- package/dist/indexer/stellar-indexer.d.ts +115 -0
- package/dist/indexer/stellar-indexer.d.ts.map +1 -0
- package/dist/indexer/stellar-indexer.js +279 -0
- package/dist/indexer/stellar-indexer.js.map +1 -0
- package/dist/indexer/types.d.ts +73 -0
- package/dist/indexer/types.d.ts.map +1 -0
- package/dist/indexer/types.js +9 -0
- package/dist/indexer/types.js.map +1 -0
- package/dist/kit/auth-payload.d.ts +89 -0
- package/dist/kit/auth-payload.d.ts.map +1 -0
- package/dist/kit/auth-payload.js +203 -0
- package/dist/kit/auth-payload.js.map +1 -0
- package/dist/kit/deploy-ops.d.ts +45 -0
- package/dist/kit/deploy-ops.d.ts.map +1 -0
- package/dist/kit/deploy-ops.js +69 -0
- package/dist/kit/deploy-ops.js.map +1 -0
- package/dist/kit/tx-ops.d.ts +92 -0
- package/dist/kit/tx-ops.d.ts.map +1 -0
- package/dist/kit/tx-ops.js +113 -0
- package/dist/kit/tx-ops.js.map +1 -0
- package/dist/kit/wallet-ops.d.ts +59 -0
- package/dist/kit/wallet-ops.d.ts.map +1 -0
- package/dist/kit/wallet-ops.js +122 -0
- package/dist/kit/wallet-ops.js.map +1 -0
- package/dist/kit/webauthn-ops.d.ts +55 -0
- package/dist/kit/webauthn-ops.d.ts.map +1 -0
- package/dist/kit/webauthn-ops.js +79 -0
- package/dist/kit/webauthn-ops.js.map +1 -0
- package/dist/kit.d.ts +128 -0
- package/dist/kit.d.ts.map +1 -0
- package/dist/kit.js +276 -0
- package/dist/kit.js.map +1 -0
- package/dist/managers/credential-manager.d.ts +39 -0
- package/dist/managers/credential-manager.d.ts.map +1 -0
- package/dist/managers/credential-manager.js +47 -0
- package/dist/managers/credential-manager.js.map +1 -0
- package/dist/managers/index.d.ts +7 -0
- package/dist/managers/index.d.ts.map +1 -0
- package/dist/managers/index.js +7 -0
- package/dist/managers/index.js.map +1 -0
- package/dist/managers/signer-manager.d.ts +51 -0
- package/dist/managers/signer-manager.d.ts.map +1 -0
- package/dist/managers/signer-manager.js +92 -0
- package/dist/managers/signer-manager.js.map +1 -0
- package/dist/managers/submission-manager.d.ts +52 -0
- package/dist/managers/submission-manager.d.ts.map +1 -0
- package/dist/managers/submission-manager.js +77 -0
- package/dist/managers/submission-manager.js.map +1 -0
- package/dist/relayer.d.ts +61 -0
- package/dist/relayer.d.ts.map +1 -0
- package/dist/relayer.js +119 -0
- package/dist/relayer.js.map +1 -0
- package/dist/rpc-data.d.ts +35 -0
- package/dist/rpc-data.d.ts.map +1 -0
- package/dist/rpc-data.js +49 -0
- package/dist/rpc-data.js.map +1 -0
- package/dist/sac.d.ts +31 -0
- package/dist/sac.d.ts.map +1 -0
- package/dist/sac.js +47 -0
- package/dist/sac.js.map +1 -0
- package/dist/server.d.ts +79 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +152 -0
- package/dist/server.js.map +1 -0
- package/dist/signers.d.ts +83 -0
- package/dist/signers.d.ts.map +1 -0
- package/dist/signers.js +124 -0
- package/dist/signers.js.map +1 -0
- package/dist/storage/index.d.ts +13 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +12 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/indexeddb.d.ts +26 -0
- package/dist/storage/indexeddb.d.ts.map +1 -0
- package/dist/storage/indexeddb.js +100 -0
- package/dist/storage/indexeddb.js.map +1 -0
- package/dist/storage/localStorage.d.ts +22 -0
- package/dist/storage/localStorage.d.ts.map +1 -0
- package/dist/storage/localStorage.js +86 -0
- package/dist/storage/localStorage.js.map +1 -0
- package/dist/storage/memory.d.ts +18 -0
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/storage/memory.js +41 -0
- package/dist/storage/memory.js.map +1 -0
- package/dist/types.d.ts +136 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +39 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +52 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +153 -0
- package/dist/utils.js.map +1 -0
- package/dist/validation.d.ts +40 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +73 -0
- package/dist/validation.js.map +1 -0
- package/dist/version.d.ts +7 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +9 -0
- package/dist/version.js.map +1 -0
- package/package.json +48 -20
- package/.claude/settings.local.json +0 -20
- package/.cursorrules +0 -170
- package/.gitattributes +0 -2
- package/.github/workflows/release.yml +0 -21
- package/.vscode/settings.json +0 -6
- package/PROPOSAL.md +0 -397
- package/cheatsheet.txt +0 -18
- package/clone-js-sdk.sh +0 -21
- package/packages/passkey-kit-sdk/README.md +0 -54
- package/packages/passkey-kit-sdk/package.json +0 -18
- package/packages/passkey-kit-sdk/src/index.ts +0 -171
- package/packages/passkey-kit-sdk/tsconfig.json +0 -98
- package/packages/passkey-kit-sdk/types/index.d.ts +0 -190
- package/packages/sac-sdk/README.md +0 -54
- package/packages/sac-sdk/package.json +0 -18
- package/packages/sac-sdk/src/index.ts +0 -586
- package/packages/sac-sdk/tsconfig.json +0 -98
- package/packages/sac-sdk/types/index.d.ts +0 -532
- package/pnpm-workspace.yaml +0 -2
- package/src/index.ts +0 -5
- package/src/kit.ts +0 -698
- package/src/sac.ts +0 -29
- package/src/server.ts +0 -179
- package/src/types.ts +0 -32
- package/tsconfig.json +0 -28
- package/types/index.d.ts +0 -5
- package/types/kit.d.ts +0 -89
- package/types/sac.d.ts +0 -13
- package/types/server.d.ts +0 -29
- package/types/types.d.ts +0 -22
package/src/kit.ts
DELETED
|
@@ -1,698 +0,0 @@
|
|
|
1
|
-
import { Client as PasskeyClient, type Signature, type SignerKey as SDKSignerKey, type SignerLimits as SDKSignerLimits } from 'passkey-kit-sdk'
|
|
2
|
-
import { StrKey, hash, xdr, Keypair, Address, TransactionBuilder, Operation } from '@stellar/stellar-sdk/minimal'
|
|
3
|
-
import { startRegistration, startAuthentication, type AuthenticationResponseJSON, type AuthenticatorAttestationResponseJSON, type AuthenticatorSelectionCriteria } from "@simplewebauthn/browser"
|
|
4
|
-
import { Buffer } from 'buffer'
|
|
5
|
-
import base64url from 'base64url'
|
|
6
|
-
import type { SignerKey, SignerLimits, SignerStore } from './types'
|
|
7
|
-
import { PasskeyBase } from './base'
|
|
8
|
-
import { AssembledTransaction, basicNodeSigner, type AssembledTransactionOptions, type Tx } from '@stellar/stellar-sdk/minimal/contract'
|
|
9
|
-
import type { Server } from '@stellar/stellar-sdk/minimal/rpc'
|
|
10
|
-
|
|
11
|
-
// TODO we should allow setting the rpId in the constructor so we don't have to keep setting it for every call
|
|
12
|
-
// e.g. if you want to sign with a smol.xyz key on next.smol.xyz key you have to set rpId over and over again
|
|
13
|
-
|
|
14
|
-
export class PasskeyKit extends PasskeyBase {
|
|
15
|
-
declare rpc: Server
|
|
16
|
-
declare rpcUrl: string
|
|
17
|
-
|
|
18
|
-
private walletKeypair: Keypair
|
|
19
|
-
private walletPublicKey: string
|
|
20
|
-
private walletWasmHash: string
|
|
21
|
-
private timeoutInSeconds: number
|
|
22
|
-
private WebAuthn: {
|
|
23
|
-
startRegistration: typeof startRegistration,
|
|
24
|
-
startAuthentication: typeof startAuthentication
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
public keyId: string | undefined
|
|
28
|
-
public networkPassphrase: string
|
|
29
|
-
public wallet: PasskeyClient | undefined
|
|
30
|
-
|
|
31
|
-
constructor(options: {
|
|
32
|
-
rpcUrl: string,
|
|
33
|
-
networkPassphrase: string,
|
|
34
|
-
walletWasmHash: string,
|
|
35
|
-
timeoutInSeconds?: number,
|
|
36
|
-
WebAuthn?: {
|
|
37
|
-
startRegistration: typeof startRegistration,
|
|
38
|
-
startAuthentication: typeof startAuthentication
|
|
39
|
-
}
|
|
40
|
-
}) {
|
|
41
|
-
const { rpcUrl, networkPassphrase, walletWasmHash, WebAuthn } = options
|
|
42
|
-
|
|
43
|
-
super(rpcUrl)
|
|
44
|
-
|
|
45
|
-
this.networkPassphrase = networkPassphrase
|
|
46
|
-
// this account exists as the seed source for deploying new wallets
|
|
47
|
-
// not using the genesis wallet as on mainnet the account has no usable signers
|
|
48
|
-
// there's a chance this isn't the best move and should instead be a constructor variable
|
|
49
|
-
// alternatively when we create a new wallet we shouldn't inherit the source as the auth entry signer
|
|
50
|
-
// Keypair.fromRawEd25519Seed(hash(Buffer.from(this.networkPassphrase)))
|
|
51
|
-
this.walletKeypair = Keypair.fromRawEd25519Seed(hash(Buffer.from('kalepail')));
|
|
52
|
-
this.walletPublicKey = this.walletKeypair.publicKey()
|
|
53
|
-
this.walletWasmHash = walletWasmHash
|
|
54
|
-
this.timeoutInSeconds = options.timeoutInSeconds || 30 // OpenZeppelin Relayer requires <= 30 second timeout so let's default to that
|
|
55
|
-
this.WebAuthn = WebAuthn || { startRegistration, startAuthentication }
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
public async createWallet(
|
|
59
|
-
app: string,
|
|
60
|
-
user: string,
|
|
61
|
-
settings?: {
|
|
62
|
-
rpId?: string
|
|
63
|
-
authenticatorSelection?: AuthenticatorSelectionCriteria
|
|
64
|
-
}
|
|
65
|
-
) {
|
|
66
|
-
const { rawResponse, keyId, keyIdBase64, publicKey } = await this.createKey(app, user, settings)
|
|
67
|
-
|
|
68
|
-
const at = await PasskeyClient.deploy(
|
|
69
|
-
{
|
|
70
|
-
signer: {
|
|
71
|
-
tag: 'Secp256r1',
|
|
72
|
-
values: [
|
|
73
|
-
keyId,
|
|
74
|
-
publicKey,
|
|
75
|
-
[undefined],
|
|
76
|
-
[undefined],
|
|
77
|
-
{ tag: 'Persistent', values: undefined },
|
|
78
|
-
]
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
rpcUrl: this.rpcUrl,
|
|
83
|
-
wasmHash: this.walletWasmHash,
|
|
84
|
-
networkPassphrase: this.networkPassphrase,
|
|
85
|
-
publicKey: this.walletPublicKey,
|
|
86
|
-
salt: hash(keyId),
|
|
87
|
-
timeoutInSeconds: this.timeoutInSeconds,
|
|
88
|
-
}
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
const contractId = at.result.options.contractId
|
|
92
|
-
|
|
93
|
-
this.wallet = new PasskeyClient({
|
|
94
|
-
contractId,
|
|
95
|
-
networkPassphrase: this.networkPassphrase,
|
|
96
|
-
rpcUrl: this.rpcUrl
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
await at.sign({
|
|
100
|
-
signTransaction: basicNodeSigner(this.walletKeypair, this.networkPassphrase).signTransaction
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
rawResponse,
|
|
105
|
-
keyId,
|
|
106
|
-
keyIdBase64,
|
|
107
|
-
contractId,
|
|
108
|
-
signedTx: at.signed!
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
public async createKey(app: string, user: string, settings?: {
|
|
113
|
-
rpId?: string
|
|
114
|
-
authenticatorSelection?: AuthenticatorSelectionCriteria
|
|
115
|
-
}) {
|
|
116
|
-
const now = new Date()
|
|
117
|
-
const displayName = `${user} — ${now.toLocaleString()}`
|
|
118
|
-
const { rpId, authenticatorSelection = {
|
|
119
|
-
residentKey: "preferred",
|
|
120
|
-
userVerification: "preferred",
|
|
121
|
-
} } = settings || {}
|
|
122
|
-
|
|
123
|
-
// TODO discover the contract id before creating the key so we can use it in the key name
|
|
124
|
-
// TODO it's possible for the creation to fail in which case we've created a passkey but it's not onchain.
|
|
125
|
-
// In this case we should save the passkey info and retry uploading it async vs asking the user to create another passkey
|
|
126
|
-
// This does introduce a storage dependency though so it likely needs to be a function with some logic for choosing how to store the passkey data
|
|
127
|
-
|
|
128
|
-
const rawResponse = await this.WebAuthn.startRegistration({
|
|
129
|
-
optionsJSON: {
|
|
130
|
-
challenge: base64url("stellaristhebetterblockchain"),
|
|
131
|
-
rp: {
|
|
132
|
-
id: rpId,
|
|
133
|
-
name: app,
|
|
134
|
-
},
|
|
135
|
-
user: {
|
|
136
|
-
id: base64url(`${user}:${now.getTime()}:${Math.random()}`),
|
|
137
|
-
name: displayName,
|
|
138
|
-
displayName
|
|
139
|
-
},
|
|
140
|
-
authenticatorSelection,
|
|
141
|
-
pubKeyCredParams: [{ alg: -7, type: "public-key" }],
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
const { id, response } = rawResponse
|
|
145
|
-
|
|
146
|
-
if (!this.keyId)
|
|
147
|
-
this.keyId = id;
|
|
148
|
-
|
|
149
|
-
return {
|
|
150
|
-
rawResponse,
|
|
151
|
-
keyId: base64url.toBuffer(id),
|
|
152
|
-
keyIdBase64: id,
|
|
153
|
-
publicKey: await this.getPublicKey(response),
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
public async connectWallet(opts?: {
|
|
158
|
-
rpId?: string,
|
|
159
|
-
keyId?: string | Uint8Array,
|
|
160
|
-
getContractId?: (keyId: string) => Promise<string | undefined>,
|
|
161
|
-
// TEMP for backwards compatibility for when we seeded wallets from a factory address
|
|
162
|
-
// Consider putting this somewhere else??
|
|
163
|
-
walletPublicKey?: string
|
|
164
|
-
}) {
|
|
165
|
-
let { rpId, keyId, getContractId, walletPublicKey } = opts || {}
|
|
166
|
-
let keyIdBuffer: Buffer
|
|
167
|
-
let rawResponse: AuthenticationResponseJSON | undefined;
|
|
168
|
-
|
|
169
|
-
if (!keyId) {
|
|
170
|
-
rawResponse = await this.WebAuthn.startAuthentication({
|
|
171
|
-
optionsJSON: {
|
|
172
|
-
challenge: base64url("stellaristhebetterblockchain"),
|
|
173
|
-
rpId,
|
|
174
|
-
userVerification: "preferred",
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
keyId = rawResponse.id
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (keyId instanceof Uint8Array) {
|
|
182
|
-
keyIdBuffer = Buffer.from(keyId)
|
|
183
|
-
keyId = base64url(keyIdBuffer)
|
|
184
|
-
} else {
|
|
185
|
-
keyIdBuffer = base64url.toBuffer(keyId)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (!this.keyId)
|
|
189
|
-
this.keyId = keyId
|
|
190
|
-
|
|
191
|
-
// Check for the contractId on-chain as a derivation from the keyId. This is the easiest and "cheapest" check however it will only work for the initially deployed passkey if it was used as derivation
|
|
192
|
-
let contractId: string | undefined = this.encodeContract(this.walletPublicKey, keyIdBuffer);
|
|
193
|
-
|
|
194
|
-
// attempt passkey id derivation
|
|
195
|
-
try {
|
|
196
|
-
// TODO what is the error if the entry exists but is archived?
|
|
197
|
-
await this.rpc.getContractData(contractId, xdr.ScVal.scvLedgerKeyContractInstance())
|
|
198
|
-
}
|
|
199
|
-
// if that fails look up from the `getContractId` function
|
|
200
|
-
catch {
|
|
201
|
-
contractId = getContractId && await getContractId(keyId)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
////
|
|
205
|
-
// TEMP for backwards compatibility for when we seeded wallets from a factory address
|
|
206
|
-
// Consider putting this in the constructor
|
|
207
|
-
if (!contractId && walletPublicKey) {
|
|
208
|
-
contractId = this.encodeContract(walletPublicKey, keyIdBuffer);
|
|
209
|
-
|
|
210
|
-
try {
|
|
211
|
-
await this.rpc.getContractData(contractId, xdr.ScVal.scvLedgerKeyContractInstance())
|
|
212
|
-
} catch {
|
|
213
|
-
contractId = undefined
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
////
|
|
217
|
-
|
|
218
|
-
if (!contractId)
|
|
219
|
-
throw new Error('Failed to connect wallet')
|
|
220
|
-
|
|
221
|
-
this.wallet = new PasskeyClient({
|
|
222
|
-
contractId,
|
|
223
|
-
rpcUrl: this.rpcUrl,
|
|
224
|
-
networkPassphrase: this.networkPassphrase,
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
return {
|
|
228
|
-
rawResponse,
|
|
229
|
-
keyId: keyIdBuffer,
|
|
230
|
-
keyIdBase64: keyId,
|
|
231
|
-
contractId
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
public async signAuthEntry(
|
|
236
|
-
entry: xdr.SorobanAuthorizationEntry,
|
|
237
|
-
options?: {
|
|
238
|
-
rpId?: string,
|
|
239
|
-
keyId?: 'any' | string | Uint8Array
|
|
240
|
-
keypair?: Keypair,
|
|
241
|
-
policy?: string,
|
|
242
|
-
expiration?: number
|
|
243
|
-
}
|
|
244
|
-
) {
|
|
245
|
-
let { rpId, keyId, keypair, policy, expiration } = options || {}
|
|
246
|
-
|
|
247
|
-
if ([keyId, keypair, policy].filter((arg) => !!arg).length > 1)
|
|
248
|
-
throw new Error('Exactly one of `options.keyId`, `options.keypair`, or `options.policy` must be provided.');
|
|
249
|
-
|
|
250
|
-
const credentials = entry.credentials().address();
|
|
251
|
-
|
|
252
|
-
if (!expiration) {
|
|
253
|
-
expiration = credentials.signatureExpirationLedger()
|
|
254
|
-
|
|
255
|
-
if (!expiration) {
|
|
256
|
-
const { sequence } = await this.rpc.getLatestLedger()
|
|
257
|
-
expiration = sequence + this.timeoutInSeconds / 5; // assumes 5 second ledger time
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
credentials.signatureExpirationLedger(expiration)
|
|
262
|
-
|
|
263
|
-
const preimage = xdr.HashIdPreimage.envelopeTypeSorobanAuthorization(
|
|
264
|
-
new xdr.HashIdPreimageSorobanAuthorization({
|
|
265
|
-
networkId: hash(Buffer.from(this.networkPassphrase)),
|
|
266
|
-
nonce: credentials.nonce(),
|
|
267
|
-
signatureExpirationLedger: credentials.signatureExpirationLedger(),
|
|
268
|
-
invocation: entry.rootInvocation()
|
|
269
|
-
})
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
const payload = hash(preimage.toXDR())
|
|
273
|
-
|
|
274
|
-
// let signatures: Signatures
|
|
275
|
-
let key: SDKSignerKey
|
|
276
|
-
let val: Signature | undefined
|
|
277
|
-
|
|
278
|
-
// const scSpecTypeDefSignatures = xdr.ScSpecTypeDef.scSpecTypeUdt(
|
|
279
|
-
// new xdr.ScSpecTypeUdt({ name: "Signatures" }),
|
|
280
|
-
// );
|
|
281
|
-
|
|
282
|
-
// switch (credentials.signature().switch()) {
|
|
283
|
-
// case xdr.ScValType.scvVoid():
|
|
284
|
-
// signatures = [new Map()]
|
|
285
|
-
// break;
|
|
286
|
-
// default: {
|
|
287
|
-
// signatures = this.wallet!.spec.scValToNative(credentials.signature(), scSpecTypeDefSignatures)
|
|
288
|
-
// }
|
|
289
|
-
// }
|
|
290
|
-
|
|
291
|
-
// Sign with a policy
|
|
292
|
-
if (policy) {
|
|
293
|
-
key = {
|
|
294
|
-
tag: "Policy",
|
|
295
|
-
values: [policy]
|
|
296
|
-
}
|
|
297
|
-
val = {
|
|
298
|
-
tag: "Policy",
|
|
299
|
-
values: undefined,
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Sign with the keypair as an ed25519 signer
|
|
304
|
-
else if (keypair) {
|
|
305
|
-
const signature = keypair.sign(payload);
|
|
306
|
-
|
|
307
|
-
key = {
|
|
308
|
-
tag: "Ed25519",
|
|
309
|
-
values: [keypair.rawPublicKey()]
|
|
310
|
-
}
|
|
311
|
-
val = {
|
|
312
|
-
tag: "Ed25519",
|
|
313
|
-
values: [signature],
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Default, use passkey
|
|
318
|
-
else {
|
|
319
|
-
const authenticationResponse = await this.WebAuthn.startAuthentication({
|
|
320
|
-
optionsJSON: keyId === 'any'
|
|
321
|
-
|| (!keyId && !this.keyId)
|
|
322
|
-
? {
|
|
323
|
-
challenge: base64url(payload),
|
|
324
|
-
rpId,
|
|
325
|
-
userVerification: "preferred",
|
|
326
|
-
}
|
|
327
|
-
: {
|
|
328
|
-
challenge: base64url(payload),
|
|
329
|
-
rpId,
|
|
330
|
-
allowCredentials: [
|
|
331
|
-
{
|
|
332
|
-
id: keyId instanceof Uint8Array
|
|
333
|
-
? base64url(Buffer.from(keyId))
|
|
334
|
-
: keyId || this.keyId!,
|
|
335
|
-
type: "public-key",
|
|
336
|
-
},
|
|
337
|
-
],
|
|
338
|
-
userVerification: "preferred",
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
key = {
|
|
343
|
-
tag: "Secp256r1",
|
|
344
|
-
values: [base64url.toBuffer(authenticationResponse.id)]
|
|
345
|
-
}
|
|
346
|
-
val = {
|
|
347
|
-
tag: "Secp256r1",
|
|
348
|
-
values: [
|
|
349
|
-
{
|
|
350
|
-
authenticator_data: base64url.toBuffer(
|
|
351
|
-
authenticationResponse.response.authenticatorData,
|
|
352
|
-
),
|
|
353
|
-
client_data_json: base64url.toBuffer(
|
|
354
|
-
authenticationResponse.response.clientDataJSON,
|
|
355
|
-
),
|
|
356
|
-
signature: this.compactSignature(
|
|
357
|
-
base64url.toBuffer(authenticationResponse.response.signature)
|
|
358
|
-
),
|
|
359
|
-
},
|
|
360
|
-
],
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const scKeyType = xdr.ScSpecTypeDef.scSpecTypeUdt(
|
|
365
|
-
new xdr.ScSpecTypeUdt({ name: "SignerKey" }),
|
|
366
|
-
);
|
|
367
|
-
const scValType = xdr.ScSpecTypeDef.scSpecTypeUdt(
|
|
368
|
-
new xdr.ScSpecTypeUdt({ name: "Signature" }),
|
|
369
|
-
);
|
|
370
|
-
const scKey = this.wallet!.spec.nativeToScVal(key, scKeyType);
|
|
371
|
-
const scVal = val ? this.wallet!.spec.nativeToScVal(val, scValType) : xdr.ScVal.scvVoid();
|
|
372
|
-
const scEntry = new xdr.ScMapEntry({
|
|
373
|
-
key: scKey,
|
|
374
|
-
val: scVal,
|
|
375
|
-
})
|
|
376
|
-
|
|
377
|
-
switch (credentials.signature().switch().name) {
|
|
378
|
-
case 'scvVoid':
|
|
379
|
-
credentials.signature(xdr.ScVal.scvVec([
|
|
380
|
-
xdr.ScVal.scvMap([scEntry])
|
|
381
|
-
]))
|
|
382
|
-
break;
|
|
383
|
-
case 'scvVec':
|
|
384
|
-
// Add the new signature to the existing map
|
|
385
|
-
credentials.signature().vec()?.[0].map()?.push(scEntry)
|
|
386
|
-
|
|
387
|
-
// Order the map by key
|
|
388
|
-
// Not using Buffer.compare because Symbols are 9 bytes and unused bytes _append_ 0s vs prepending them, which is too bad
|
|
389
|
-
credentials.signature().vec()?.[0].map()?.sort((a, b) => {
|
|
390
|
-
return (
|
|
391
|
-
a.key().vec()![0].sym() +
|
|
392
|
-
a.key().vec()![1].toXDR().join('')
|
|
393
|
-
).localeCompare(
|
|
394
|
-
b.key().vec()![0].sym() +
|
|
395
|
-
b.key().vec()![1].toXDR().join('')
|
|
396
|
-
)
|
|
397
|
-
})
|
|
398
|
-
break;
|
|
399
|
-
default:
|
|
400
|
-
throw new Error('Unsupported signature')
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Insert the new signature into the signatures Map
|
|
404
|
-
// signatures[0].set(key, val)
|
|
405
|
-
|
|
406
|
-
// Insert the new signatures Map into the credentials
|
|
407
|
-
// credentials.signature(
|
|
408
|
-
// this.wallet!.spec.nativeToScVal(signatures, scSpecTypeDefSignatures)
|
|
409
|
-
// )
|
|
410
|
-
|
|
411
|
-
// Order the signatures map
|
|
412
|
-
// credentials.signature().vec()?.[0].map()?.sort((a, b) => {
|
|
413
|
-
// return (
|
|
414
|
-
// a.key().vec()![0].sym() +
|
|
415
|
-
// a.key().vec()![1].toXDR().join('')
|
|
416
|
-
// ).localeCompare(
|
|
417
|
-
// b.key().vec()![0].sym() +
|
|
418
|
-
// b.key().vec()![1].toXDR().join('')
|
|
419
|
-
// )
|
|
420
|
-
// })
|
|
421
|
-
|
|
422
|
-
return entry
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
public async sign<T>(
|
|
426
|
-
txn: AssembledTransaction<T> | Tx | string,
|
|
427
|
-
options?: {
|
|
428
|
-
rpId?: string,
|
|
429
|
-
keyId?: 'any' | string | Uint8Array
|
|
430
|
-
keypair?: Keypair,
|
|
431
|
-
policy?: string,
|
|
432
|
-
expiration?: number
|
|
433
|
-
}
|
|
434
|
-
) {
|
|
435
|
-
if (!(txn instanceof AssembledTransaction)) {
|
|
436
|
-
try {
|
|
437
|
-
txn = AssembledTransaction.fromXDR(this.wallet!.options, typeof txn === 'string' ? txn : txn.toXDR(), this.wallet!.spec);
|
|
438
|
-
} catch {
|
|
439
|
-
if (!(txn instanceof AssembledTransaction)) {
|
|
440
|
-
const built = TransactionBuilder.fromXDR(typeof txn === 'string' ? txn : txn.toXDR(), this.networkPassphrase);
|
|
441
|
-
const operation = built.operations[0] as Operation.InvokeHostFunction;
|
|
442
|
-
|
|
443
|
-
txn = await AssembledTransaction.buildWithOp<T>(
|
|
444
|
-
Operation.invokeHostFunction({ func: operation.func }),
|
|
445
|
-
this.wallet!.options as AssembledTransactionOptions<T>
|
|
446
|
-
);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
await txn.signAuthEntries({
|
|
452
|
-
address: this.wallet!.options.contractId,
|
|
453
|
-
authorizeEntry: (entry) => {
|
|
454
|
-
const clone = xdr.SorobanAuthorizationEntry.fromXDR(entry.toXDR())
|
|
455
|
-
return this.signAuthEntry(clone, options)
|
|
456
|
-
},
|
|
457
|
-
})
|
|
458
|
-
|
|
459
|
-
return txn
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
public addSecp256r1(keyId: string | Uint8Array, publicKey: string | Uint8Array, limits: SignerLimits, store: SignerStore, expiration?: number) {
|
|
463
|
-
return this.secp256r1(keyId, publicKey, limits, store, 'add_signer', expiration)
|
|
464
|
-
}
|
|
465
|
-
public addEd25519(publicKey: string, limits: SignerLimits, store: SignerStore, expiration?: number) {
|
|
466
|
-
return this.ed25519(publicKey, limits, store, 'add_signer', expiration)
|
|
467
|
-
}
|
|
468
|
-
public addPolicy(policy: string, limits: SignerLimits, store: SignerStore, expiration?: number) {
|
|
469
|
-
return this.policy(policy, limits, store, 'add_signer', expiration)
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
public updateSecp256r1(keyId: string | Uint8Array, publicKey: string | Uint8Array, limits: SignerLimits, store: SignerStore, expiration?: number) {
|
|
473
|
-
return this.secp256r1(keyId, publicKey, limits, store, 'update_signer', expiration)
|
|
474
|
-
}
|
|
475
|
-
public updateEd25519(publicKey: string, limits: SignerLimits, store: SignerStore, expiration?: number) {
|
|
476
|
-
return this.ed25519(publicKey, limits, store, 'update_signer', expiration)
|
|
477
|
-
}
|
|
478
|
-
public updatePolicy(policy: string, limits: SignerLimits, store: SignerStore, expiration?: number) {
|
|
479
|
-
return this.policy(policy, limits, store, 'update_signer', expiration)
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
public remove(signer: SignerKey) {
|
|
483
|
-
return this.wallet!.remove_signer({
|
|
484
|
-
signer_key: this.getSignerKey(signer)
|
|
485
|
-
}, {
|
|
486
|
-
timeoutInSeconds: this.timeoutInSeconds,
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
private secp256r1(keyId: string | Uint8Array, publicKey: string | Uint8Array, limits: SignerLimits, store: SignerStore, fn: 'add_signer' | 'update_signer', expiration?: number) {
|
|
491
|
-
keyId = typeof keyId === 'string' ? base64url.toBuffer(keyId) : keyId
|
|
492
|
-
publicKey = typeof publicKey === 'string' ? base64url.toBuffer(publicKey) : publicKey
|
|
493
|
-
|
|
494
|
-
return this.wallet,
|
|
499
|
-
Buffer.from(publicKey),
|
|
500
|
-
[expiration],
|
|
501
|
-
this.getSignerLimits(limits),
|
|
502
|
-
{ tag: store, values: undefined },
|
|
503
|
-
],
|
|
504
|
-
},
|
|
505
|
-
}, {
|
|
506
|
-
timeoutInSeconds: this.timeoutInSeconds,
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
private ed25519(publicKey: string, limits: SignerLimits, store: SignerStore, fn: 'add_signer' | 'update_signer', expiration?: number) {
|
|
510
|
-
return this.wallet.rawPublicKey(),
|
|
515
|
-
[expiration],
|
|
516
|
-
this.getSignerLimits(limits),
|
|
517
|
-
{ tag: store, values: undefined },
|
|
518
|
-
],
|
|
519
|
-
},
|
|
520
|
-
}, {
|
|
521
|
-
timeoutInSeconds: this.timeoutInSeconds,
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
private policy(policy: string, limits: SignerLimits, store: SignerStore, fn: 'add_signer' | 'update_signer', expiration?: number) {
|
|
525
|
-
return this.wallet,
|
|
532
|
-
{ tag: store, values: undefined },
|
|
533
|
-
],
|
|
534
|
-
},
|
|
535
|
-
}, {
|
|
536
|
-
timeoutInSeconds: this.timeoutInSeconds,
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
/* LATER
|
|
541
|
-
- Add a getKeyInfo action to get info about a specific passkey
|
|
542
|
-
Specifically looking for name, type, etc. data so a user could grok what signer mapped to what passkey
|
|
543
|
-
*/
|
|
544
|
-
|
|
545
|
-
private getSignerLimits(limits: SignerLimits) {
|
|
546
|
-
if (!limits)
|
|
547
|
-
return [undefined] as SDKSignerLimits
|
|
548
|
-
|
|
549
|
-
const sdk_limits: SDKSignerLimits = [new Map()]
|
|
550
|
-
|
|
551
|
-
for (const [contract, signer_keys] of limits.entries()) {
|
|
552
|
-
let sdk_signer_keys: SDKSignerKey[] | undefined
|
|
553
|
-
|
|
554
|
-
if (signer_keys?.length) {
|
|
555
|
-
sdk_signer_keys = []
|
|
556
|
-
|
|
557
|
-
for (const signer_key of signer_keys) {
|
|
558
|
-
sdk_signer_keys.push(
|
|
559
|
-
this.getSignerKey(signer_key)
|
|
560
|
-
)
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
sdk_limits[0]?.set(contract, sdk_signer_keys)
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
return sdk_limits
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
private getSignerKey({ key: tag, value }: SignerKey) {
|
|
571
|
-
let signer_key: SDKSignerKey
|
|
572
|
-
|
|
573
|
-
switch (tag) {
|
|
574
|
-
case 'Policy':
|
|
575
|
-
signer_key = {
|
|
576
|
-
tag,
|
|
577
|
-
values: [value]
|
|
578
|
-
}
|
|
579
|
-
break;
|
|
580
|
-
case 'Ed25519':
|
|
581
|
-
signer_key = {
|
|
582
|
-
tag,
|
|
583
|
-
values: [Keypair.fromPublicKey(value).rawPublicKey()]
|
|
584
|
-
}
|
|
585
|
-
break;
|
|
586
|
-
case 'Secp256r1':
|
|
587
|
-
signer_key = {
|
|
588
|
-
tag,
|
|
589
|
-
values: [base64url.toBuffer(value)]
|
|
590
|
-
}
|
|
591
|
-
break;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
return signer_key
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
private encodeContract(walletPublicKey: string, keyIdBuffer: Buffer) {
|
|
598
|
-
let contractId: string | undefined = StrKey.encodeContract(hash(xdr.HashIdPreimage.envelopeTypeContractId(
|
|
599
|
-
new xdr.HashIdPreimageContractId({
|
|
600
|
-
networkId: hash(Buffer.from(this.networkPassphrase)),
|
|
601
|
-
contractIdPreimage: xdr.ContractIdPreimage.contractIdPreimageFromAddress(
|
|
602
|
-
new xdr.ContractIdPreimageFromAddress({
|
|
603
|
-
address: Address.fromString(walletPublicKey).toScAddress(),
|
|
604
|
-
salt: hash(keyIdBuffer),
|
|
605
|
-
})
|
|
606
|
-
)
|
|
607
|
-
})
|
|
608
|
-
).toXDR()));
|
|
609
|
-
|
|
610
|
-
return contractId
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
private async getPublicKey(response: AuthenticatorAttestationResponseJSON) {
|
|
614
|
-
let publicKey: Buffer | undefined
|
|
615
|
-
|
|
616
|
-
if (response.publicKey) {
|
|
617
|
-
publicKey = base64url.toBuffer(response.publicKey)
|
|
618
|
-
publicKey = publicKey?.slice(publicKey.length - 65)
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
if (
|
|
622
|
-
!publicKey
|
|
623
|
-
|| publicKey[0] !== 0x04
|
|
624
|
-
|| publicKey.length !== 65
|
|
625
|
-
) {
|
|
626
|
-
let x: Buffer
|
|
627
|
-
let y: Buffer
|
|
628
|
-
|
|
629
|
-
if (response.authenticatorData) {
|
|
630
|
-
const authenticatorData = base64url.toBuffer(response.authenticatorData)
|
|
631
|
-
const credentialIdLength = (authenticatorData[53] << 8) | authenticatorData[54]
|
|
632
|
-
|
|
633
|
-
x = authenticatorData.slice(65 + credentialIdLength, 97 + credentialIdLength)
|
|
634
|
-
y = authenticatorData.slice(100 + credentialIdLength, 132 + credentialIdLength)
|
|
635
|
-
} else {
|
|
636
|
-
const attestationObject = base64url.toBuffer(response.attestationObject)
|
|
637
|
-
|
|
638
|
-
let publicKeykPrefixSlice = Buffer.from([0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20])
|
|
639
|
-
let startIndex = attestationObject.indexOf(publicKeykPrefixSlice)
|
|
640
|
-
startIndex = startIndex + publicKeykPrefixSlice.length
|
|
641
|
-
|
|
642
|
-
x = attestationObject.slice(startIndex, 32 + startIndex)
|
|
643
|
-
y = attestationObject.slice(35 + startIndex, 67 + startIndex)
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
publicKey = Buffer.from([
|
|
647
|
-
0x04, // (0x04 prefix) https://en.bitcoin.it/wiki/Elliptic_Curve_Digital_Signature_Algorithm
|
|
648
|
-
...x,
|
|
649
|
-
...y
|
|
650
|
-
])
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
/* TODO
|
|
654
|
-
- We're doing some pretty "smart" public key decoding stuff so we should verify the signature against this final public key before assuming it's safe to use and save on-chain
|
|
655
|
-
Hmm...Given that `startRegistration` doesn't produce a signature, verifying we've got the correct public key isn't really possible
|
|
656
|
-
- This probably needs to be an onchain check, even if just a simulation, just to ensure everything looks good before we get too far adding value etc.
|
|
657
|
-
*/
|
|
658
|
-
|
|
659
|
-
return publicKey
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
private compactSignature(signature: Buffer) {
|
|
663
|
-
// Decode the DER signature
|
|
664
|
-
let offset = 2;
|
|
665
|
-
|
|
666
|
-
const rLength = signature[offset + 1];
|
|
667
|
-
const r = signature.slice(offset + 2, offset + 2 + rLength);
|
|
668
|
-
|
|
669
|
-
offset += 2 + rLength;
|
|
670
|
-
|
|
671
|
-
const sLength = signature[offset + 1];
|
|
672
|
-
const s = signature.slice(offset + 2, offset + 2 + sLength);
|
|
673
|
-
|
|
674
|
-
// Convert r and s to BigInt
|
|
675
|
-
const rBigInt = BigInt('0x' + r.toString('hex'));
|
|
676
|
-
let sBigInt = BigInt('0x' + s.toString('hex'));
|
|
677
|
-
|
|
678
|
-
// Ensure s is in the low-S form
|
|
679
|
-
// https://github.com/stellar/stellar-protocol/discussions/1435#discussioncomment-8809175
|
|
680
|
-
// https://discord.com/channels/897514728459468821/1233048618571927693
|
|
681
|
-
// Define the order of the curve secp256r1
|
|
682
|
-
// https://github.com/RustCrypto/elliptic-curves/blob/master/p256/src/lib.rs#L72
|
|
683
|
-
const n = BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551');
|
|
684
|
-
const halfN = n / 2n;
|
|
685
|
-
|
|
686
|
-
if (sBigInt > halfN)
|
|
687
|
-
sBigInt = n - sBigInt;
|
|
688
|
-
|
|
689
|
-
// Convert back to buffers and ensure they are 32 bytes
|
|
690
|
-
const rPadded = Buffer.from(rBigInt.toString(16).padStart(64, '0'), 'hex');
|
|
691
|
-
const sLowS = Buffer.from(sBigInt.toString(16).padStart(64, '0'), 'hex');
|
|
692
|
-
|
|
693
|
-
// Concatenate r and low-s
|
|
694
|
-
const concatSignature = Buffer.concat([rPadded, sLowS]);
|
|
695
|
-
|
|
696
|
-
return concatSignature;
|
|
697
|
-
}
|
|
698
|
-
}
|