passkey-kit 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.
@@ -15,21 +15,18 @@ if (typeof window !== 'undefined') {
15
15
  export const networks = {
16
16
  testnet: {
17
17
  networkPassphrase: "Test SDF Network ; September 2015",
18
- contractId: "CA6DVSXLLOSI32JAPGP4MLOG64FGERLYCP2SGRX2A6MPWGA5KDUKL66P",
18
+ contractId: "CCPLERXCJZB7LX2VOSOCBNRN754FRLHI6Y2AVOQBA5L7C2ZJX5RFVVET",
19
19
  }
20
20
  } as const
21
21
 
22
22
  export const Errors = {
23
- 1: { message: "" },
24
- 2: { message: "" },
25
- 3: { message: "" },
26
- 4: { message: "" },
27
- 5: { message: "" },
28
- 6: { message: "" },
29
- 7: { message: "" },
30
- 8: { message: "" },
31
- 9: { message: "" },
32
- 10: { message: "" }
23
+ 1: { message: "NotFound" },
24
+ 2: { message: "NotPermitted" },
25
+ 3: { message: "ClientDataJsonChallengeIncorrect" },
26
+ 4: { message: "Secp256r1PublicKeyParse" },
27
+ 5: { message: "Secp256r1SignatureParse" },
28
+ 6: { message: "Secp256r1VerifyFailed" },
29
+ 7: { message: "JsonParseError" }
33
30
  }
34
31
 
35
32
  export interface Signature {
@@ -41,9 +38,9 @@ export interface Signature {
41
38
 
42
39
  export interface Client {
43
40
  /**
44
- * Construct and simulate a init transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
41
+ * Construct and simulate a add transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
45
42
  */
46
- init: ({ id, pk }: { id: Buffer, pk: Buffer }, options?: {
43
+ add: ({ id, pk, admin }: { id: Buffer, pk: Buffer, admin: boolean }, options?: {
47
44
  /**
48
45
  * The fee to pay for the transaction. Default: BASE_FEE
49
46
  */
@@ -61,9 +58,9 @@ export interface Client {
61
58
  }) => Promise<AssembledTransaction<Result<void>>>
62
59
 
63
60
  /**
64
- * Construct and simulate a upgrade transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
61
+ * Construct and simulate a remove transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
65
62
  */
66
- upgrade: ({ hash }: { hash: Buffer }, options?: {
63
+ remove: ({ id }: { id: Buffer }, options?: {
67
64
  /**
68
65
  * The fee to pay for the transaction. Default: BASE_FEE
69
66
  */
@@ -81,49 +78,9 @@ export interface Client {
81
78
  }) => Promise<AssembledTransaction<Result<void>>>
82
79
 
83
80
  /**
84
- * Construct and simulate a add_sig transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
81
+ * Construct and simulate a update transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
85
82
  */
86
- add_sig: ({ id, pk }: { id: Buffer, pk: Buffer }, options?: {
87
- /**
88
- * The fee to pay for the transaction. Default: BASE_FEE
89
- */
90
- fee?: number;
91
-
92
- /**
93
- * The maximum amount of time to wait for the transaction to complete. Default: DEFAULT_TIMEOUT
94
- */
95
- timeoutInSeconds?: number;
96
-
97
- /**
98
- * Whether to automatically simulate the transaction when constructing the AssembledTransaction. Default: true
99
- */
100
- simulate?: boolean;
101
- }) => Promise<AssembledTransaction<Result<void>>>
102
-
103
- /**
104
- * Construct and simulate a rm_sig transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
105
- */
106
- rm_sig: ({ id }: { id: Buffer }, options?: {
107
- /**
108
- * The fee to pay for the transaction. Default: BASE_FEE
109
- */
110
- fee?: number;
111
-
112
- /**
113
- * The maximum amount of time to wait for the transaction to complete. Default: DEFAULT_TIMEOUT
114
- */
115
- timeoutInSeconds?: number;
116
-
117
- /**
118
- * Whether to automatically simulate the transaction when constructing the AssembledTransaction. Default: true
119
- */
120
- simulate?: boolean;
121
- }) => Promise<AssembledTransaction<Result<void>>>
122
-
123
- /**
124
- * Construct and simulate a re_super transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
125
- */
126
- re_super: ({ id }: { id: Buffer }, options?: {
83
+ update: ({ hash }: { hash: Buffer }, options?: {
127
84
  /**
128
85
  * The fee to pay for the transaction. Default: BASE_FEE
129
86
  */
@@ -143,22 +100,18 @@ export interface Client {
143
100
  export class Client extends ContractClient {
144
101
  constructor(public readonly options: ContractClientOptions) {
145
102
  super(
146
- new ContractSpec(["AAAABAAAAAAAAAAAAAAABUVycm9yAAAAAAAACgAAAAAAAAAOTm90SW5pdGlhbGl6ZWQAAAAAAAEAAAAAAAAACE5vdEZvdW5kAAAAAgAAAAAAAAAMTm90UGVybWl0dGVkAAAAAwAAAAAAAAASQWxyZWFkeUluaXRpYWxpemVkAAAAAAAEAAAAAAAAAA5Kc29uUGFyc2VFcnJvcgAAAAAABQAAAAAAAAAOSW52YWxpZENvbnRleHQAAAAAAAYAAAAAAAAAIENsaWVudERhdGFKc29uQ2hhbGxlbmdlSW5jb3JyZWN0AAAABwAAAAAAAAAXU2VjcDI1NnIxUHVibGljS2V5UGFyc2UAAAAACAAAAAAAAAAXU2VjcDI1NnIxU2lnbmF0dXJlUGFyc2UAAAAACQAAAAAAAAAVU2VjcDI1NnIxVmVyaWZ5RmFpbGVkAAAAAAAACg==",
147
- "AAAAAAAAAAAAAAAEaW5pdAAAAAIAAAAAAAAAAmlkAAAAAAAOAAAAAAAAAAJwawAAAAAD7gAAAEEAAAABAAAD6QAAA+0AAAAAAAAAAw==",
148
- "AAAAAAAAAAAAAAAHdXBncmFkZQAAAAABAAAAAAAAAARoYXNoAAAD7gAAACAAAAABAAAD6QAAA+0AAAAAAAAAAw==",
149
- "AAAAAAAAAAAAAAAHYWRkX3NpZwAAAAACAAAAAAAAAAJpZAAAAAAADgAAAAAAAAACcGsAAAAAA+4AAABBAAAAAQAAA+kAAAPtAAAAAAAAAAM=",
150
- "AAAAAAAAAAAAAAAGcm1fc2lnAAAAAAABAAAAAAAAAAJpZAAAAAAADgAAAAEAAAPpAAAD7QAAAAAAAAAD",
151
- "AAAAAAAAAAAAAAAIcmVfc3VwZXIAAAABAAAAAAAAAAJpZAAAAAAADgAAAAEAAAPpAAAD7QAAAAAAAAAD",
103
+ new ContractSpec(["AAAABAAAAAAAAAAAAAAABUVycm9yAAAAAAAABwAAAAAAAAAITm90Rm91bmQAAAABAAAAAAAAAAxOb3RQZXJtaXR0ZWQAAAACAAAAAAAAACBDbGllbnREYXRhSnNvbkNoYWxsZW5nZUluY29ycmVjdAAAAAMAAAAAAAAAF1NlY3AyNTZyMVB1YmxpY0tleVBhcnNlAAAAAAQAAAAAAAAAF1NlY3AyNTZyMVNpZ25hdHVyZVBhcnNlAAAAAAUAAAAAAAAAFVNlY3AyNTZyMVZlcmlmeUZhaWxlZAAAAAAAAAYAAAAAAAAADkpzb25QYXJzZUVycm9yAAAAAAAH",
104
+ "AAAAAAAAAAAAAAADYWRkAAAAAAMAAAAAAAAAAmlkAAAAAAAOAAAAAAAAAAJwawAAAAAD7gAAAEEAAAAAAAAABWFkbWluAAAAAAAAAQAAAAEAAAPpAAAD7QAAAAAAAAAD",
105
+ "AAAAAAAAAAAAAAAGcmVtb3ZlAAAAAAABAAAAAAAAAAJpZAAAAAAADgAAAAEAAAPpAAAD7QAAAAAAAAAD",
106
+ "AAAAAAAAAAAAAAAGdXBkYXRlAAAAAAABAAAAAAAAAARoYXNoAAAD7gAAACAAAAABAAAD6QAAA+0AAAAAAAAAAw==",
152
107
  "AAAAAQAAAAAAAAAAAAAACVNpZ25hdHVyZQAAAAAAAAQAAAAAAAAAEmF1dGhlbnRpY2F0b3JfZGF0YQAAAAAADgAAAAAAAAAQY2xpZW50X2RhdGFfanNvbgAAAA4AAAAAAAAAAmlkAAAAAAAOAAAAAAAAAAlzaWduYXR1cmUAAAAAAAPuAAAAQA==",
153
108
  "AAAAAAAAAAAAAAAMX19jaGVja19hdXRoAAAAAwAAAAAAAAARc2lnbmF0dXJlX3BheWxvYWQAAAAAAAPuAAAAIAAAAAAAAAAJc2lnbmF0dXJlAAAAAAAH0AAAAAlTaWduYXR1cmUAAAAAAAAAAAAADWF1dGhfY29udGV4dHMAAAAAAAPqAAAH0AAAAAdDb250ZXh0AAAAAAEAAAPpAAAD7QAAAAAAAAAD"]),
154
109
  options
155
110
  )
156
111
  }
157
112
  public readonly fromJSON = {
158
- init: this.txFromJSON<Result<void>>,
159
- upgrade: this.txFromJSON<Result<void>>,
160
- add_sig: this.txFromJSON<Result<void>>,
161
- rm_sig: this.txFromJSON<Result<void>>,
162
- re_super: this.txFromJSON<Result<void>>
113
+ add: this.txFromJSON<Result<void>>,
114
+ remove: this.txFromJSON<Result<void>>,
115
+ update: this.txFromJSON<Result<void>>
163
116
  }
164
117
  }
@@ -3,7 +3,7 @@ import { AssembledTransaction, Client as ContractClient, ClientOptions as Contra
3
3
  export declare const networks: {
4
4
  readonly testnet: {
5
5
  readonly networkPassphrase: "Test SDF Network ; September 2015";
6
- readonly contractId: "CA6DVSXLLOSI32JAPGP4MLOG64FGERLYCP2SGRX2A6MPWGA5KDUKL66P";
6
+ readonly contractId: "CCPLERXCJZB7LX2VOSOCBNRN754FRLHI6Y2AVOQBA5L7C2ZJX5RFVVET";
7
7
  };
8
8
  };
9
9
  export declare const Errors: {
@@ -28,15 +28,6 @@ export declare const Errors: {
28
28
  7: {
29
29
  message: string;
30
30
  };
31
- 8: {
32
- message: string;
33
- };
34
- 9: {
35
- message: string;
36
- };
37
- 10: {
38
- message: string;
39
- };
40
31
  };
41
32
  export interface Signature {
42
33
  authenticator_data: Buffer;
@@ -46,11 +37,12 @@ export interface Signature {
46
37
  }
47
38
  export interface Client {
48
39
  /**
49
- * Construct and simulate a init transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
40
+ * Construct and simulate a add transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
50
41
  */
51
- init: ({ id, pk }: {
42
+ add: ({ id, pk, admin }: {
52
43
  id: Buffer;
53
44
  pk: Buffer;
45
+ admin: boolean;
54
46
  }, options?: {
55
47
  /**
56
48
  * The fee to pay for the transaction. Default: BASE_FEE
@@ -66,30 +58,10 @@ export interface Client {
66
58
  simulate?: boolean;
67
59
  }) => Promise<AssembledTransaction<Result<void>>>;
68
60
  /**
69
- * Construct and simulate a upgrade transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
61
+ * Construct and simulate a remove transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
70
62
  */
71
- upgrade: ({ hash }: {
72
- hash: Buffer;
73
- }, options?: {
74
- /**
75
- * The fee to pay for the transaction. Default: BASE_FEE
76
- */
77
- fee?: number;
78
- /**
79
- * The maximum amount of time to wait for the transaction to complete. Default: DEFAULT_TIMEOUT
80
- */
81
- timeoutInSeconds?: number;
82
- /**
83
- * Whether to automatically simulate the transaction when constructing the AssembledTransaction. Default: true
84
- */
85
- simulate?: boolean;
86
- }) => Promise<AssembledTransaction<Result<void>>>;
87
- /**
88
- * Construct and simulate a add_sig transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
89
- */
90
- add_sig: ({ id, pk }: {
63
+ remove: ({ id }: {
91
64
  id: Buffer;
92
- pk: Buffer;
93
65
  }, options?: {
94
66
  /**
95
67
  * The fee to pay for the transaction. Default: BASE_FEE
@@ -105,29 +77,10 @@ export interface Client {
105
77
  simulate?: boolean;
106
78
  }) => Promise<AssembledTransaction<Result<void>>>;
107
79
  /**
108
- * Construct and simulate a rm_sig transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
80
+ * Construct and simulate a update transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
109
81
  */
110
- rm_sig: ({ id }: {
111
- id: Buffer;
112
- }, options?: {
113
- /**
114
- * The fee to pay for the transaction. Default: BASE_FEE
115
- */
116
- fee?: number;
117
- /**
118
- * The maximum amount of time to wait for the transaction to complete. Default: DEFAULT_TIMEOUT
119
- */
120
- timeoutInSeconds?: number;
121
- /**
122
- * Whether to automatically simulate the transaction when constructing the AssembledTransaction. Default: true
123
- */
124
- simulate?: boolean;
125
- }) => Promise<AssembledTransaction<Result<void>>>;
126
- /**
127
- * Construct and simulate a re_super transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object.
128
- */
129
- re_super: ({ id }: {
130
- id: Buffer;
82
+ update: ({ hash }: {
83
+ hash: Buffer;
131
84
  }, options?: {
132
85
  /**
133
86
  * The fee to pay for the transaction. Default: BASE_FEE
@@ -147,10 +100,8 @@ export declare class Client extends ContractClient {
147
100
  readonly options: ContractClientOptions;
148
101
  constructor(options: ContractClientOptions);
149
102
  readonly fromJSON: {
150
- init: (json: string) => AssembledTransaction<Result<void, import("@stellar/stellar-sdk/contract").ErrorMessage>>;
151
- upgrade: (json: string) => AssembledTransaction<Result<void, import("@stellar/stellar-sdk/contract").ErrorMessage>>;
152
- add_sig: (json: string) => AssembledTransaction<Result<void, import("@stellar/stellar-sdk/contract").ErrorMessage>>;
153
- rm_sig: (json: string) => AssembledTransaction<Result<void, import("@stellar/stellar-sdk/contract").ErrorMessage>>;
154
- re_super: (json: string) => AssembledTransaction<Result<void, import("@stellar/stellar-sdk/contract").ErrorMessage>>;
103
+ add: (json: string) => AssembledTransaction<Result<void, import("@stellar/stellar-sdk/contract").ErrorMessage>>;
104
+ remove: (json: string) => AssembledTransaction<Result<void, import("@stellar/stellar-sdk/contract").ErrorMessage>>;
105
+ update: (json: string) => AssembledTransaction<Result<void, import("@stellar/stellar-sdk/contract").ErrorMessage>>;
155
106
  };
156
107
  }
package/src/kit.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { Client as PasskeyClient } from 'passkey-kit-sdk'
2
2
  import { Client as FactoryClient } from 'passkey-factory-sdk'
3
- import { Address, Networks, StrKey, hash, xdr, Transaction, SorobanRpc, Operation, scValToNative, TransactionBuilder } from '@stellar/stellar-sdk'
4
- import { bufToBigint, bigintToBuf } from 'bigint-conversion'
3
+ import { Address, Networks, StrKey, hash, xdr, Transaction, SorobanRpc, Operation, TransactionBuilder } from '@stellar/stellar-sdk'
5
4
  import base64url from 'base64url'
6
5
  import { startRegistration, startAuthentication } from "@simplewebauthn/browser"
6
+ import type { AuthenticatorAttestationResponseJSON } from "@simplewebauthn/types"
7
7
  import { decode } from 'cbor-x/decode'
8
8
  import { Buffer } from 'buffer'
9
9
  import { PasskeyBase } from './base'
@@ -12,7 +12,6 @@ type GetContractIdFunction = (keyId: string) => Promise<string>;
12
12
 
13
13
  export class PasskeyKit extends PasskeyBase {
14
14
  public keyId: string | undefined
15
- public keyExpired: boolean | undefined
16
15
  public wallet: PasskeyClient | undefined
17
16
  public factory: FactoryClient
18
17
  public networkPassphrase: Networks
@@ -49,12 +48,13 @@ export class PasskeyKit extends PasskeyBase {
49
48
  })
50
49
  }
51
50
 
52
- public async createWallet(name: string, user: string) {
53
- const { keyId, publicKey } = await this.createKey(name, user)
51
+ public async createWallet(app: string, user: string) {
52
+ const { keyId, publicKey } = await this.createKey(app, user)
54
53
 
55
54
  const { result, built } = await this.factory.deploy({
55
+ salt: hash(keyId),
56
56
  id: keyId,
57
- pk: publicKey!
57
+ pk: publicKey
58
58
  })
59
59
 
60
60
  const contractId = result.unwrap() as string
@@ -72,17 +72,19 @@ export class PasskeyKit extends PasskeyBase {
72
72
  }
73
73
  }
74
74
 
75
- public async createKey(name: string, user: string) {
76
- const startRegistrationResponse = await startRegistration({
75
+ public async createKey(app: string, user: string) {
76
+ const now = new Date()
77
+ const displayName = `${user} — ${now.toLocaleString()}`
78
+ const { id, response} = await startRegistration({
77
79
  challenge: base64url("stellaristhebetterblockchain"),
78
80
  rp: {
79
81
  // id: undefined,
80
- name,
82
+ name: app,
81
83
  },
82
84
  user: {
83
- id: base64url(user),
84
- name: user,
85
- displayName: user,
85
+ id: base64url(`${user}:${now.getTime()}:${Math.random()}`),
86
+ name: displayName,
87
+ displayName
86
88
  },
87
89
  authenticatorSelection: {
88
90
  requireResidentKey: false,
@@ -94,19 +96,11 @@ export class PasskeyKit extends PasskeyBase {
94
96
  });
95
97
 
96
98
  if (!this.keyId)
97
- this.keyId = startRegistrationResponse.id
98
-
99
- const { publicKeyObject } = this.getPublicKeyObject(startRegistrationResponse.response.attestationObject);
100
-
101
- const publicKey = Buffer.from([
102
- 4, // (0x04 prefix) https://en.bitcoin.it/wiki/Elliptic_Curve_Digital_Signature_Algorithm
103
- ...publicKeyObject.get('-2')!,
104
- ...publicKeyObject.get('-3')!
105
- ])
99
+ this.keyId = id;
106
100
 
107
101
  return {
108
- keyId: base64url.toBuffer(startRegistrationResponse.id),
109
- publicKey
102
+ keyId: base64url.toBuffer(id),
103
+ publicKey: this.getPublicKey(response)
110
104
  }
111
105
  }
112
106
 
@@ -115,21 +109,23 @@ export class PasskeyKit extends PasskeyBase {
115
109
  getContractId?: GetContractIdFunction
116
110
  }) {
117
111
  let { keyId, getContractId } = opts
118
- let keyIdBuffer: Uint8Array
112
+ let keyIdBuffer: Buffer
119
113
 
120
114
  if (!keyId) {
121
- const { id } = await startAuthentication({
115
+ const response = await startAuthentication({
122
116
  challenge: base64url("stellaristhebetterblockchain"),
123
117
  // rpId: undefined,
124
118
  userVerification: "discouraged",
125
119
  });
126
120
 
127
- keyId = id
121
+ console.log(response);
122
+
123
+ keyId = response.id
128
124
  }
129
125
 
130
126
  if (keyId instanceof Uint8Array) {
131
- keyIdBuffer = keyId
132
- keyId = base64url(keyId)
127
+ keyIdBuffer = Buffer.from(keyId)
128
+ keyId = base64url(keyIdBuffer)
133
129
  } else {
134
130
  keyIdBuffer = base64url.toBuffer(keyId)
135
131
  }
@@ -140,7 +136,7 @@ export class PasskeyKit extends PasskeyBase {
140
136
  // 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
141
137
  let contractId: string | undefined = StrKey.encodeContract(hash(xdr.HashIdPreimage.envelopeTypeContractId(
142
138
  new xdr.HashIdPreimageContractId({
143
- networkId: hash(Buffer.from(this.networkPassphrase, 'utf-8')),
139
+ networkId: hash(Buffer.from(this.networkPassphrase)),
144
140
  contractIdPreimage: xdr.ContractIdPreimage.contractIdPreimageFromAddress(
145
141
  new xdr.ContractIdPreimageFromAddress({
146
142
  address: Address.fromString(this.factory.options.contractId).toScAddress(),
@@ -152,23 +148,15 @@ export class PasskeyKit extends PasskeyBase {
152
148
 
153
149
  // attempt passkey id derivation
154
150
  try {
151
+ // TODO what is the error if the entry exists but is archived?
155
152
  await this.rpc.getContractData(contractId, xdr.ScVal.scvLedgerKeyContractInstance())
156
153
  }
157
154
  // if that fails look up from the factory mapper
158
155
  catch {
159
156
  contractId = undefined
160
157
 
161
- if (getContractId) {
158
+ if (getContractId)
162
159
  contractId = await getContractId(keyId)
163
-
164
- // Handle case where temporary session signer is missing on-chain (so we can queue up a re-add)
165
- try {
166
- await this.rpc.getContractData(contractId, xdr.ScVal.scvBytes(keyIdBuffer), SorobanRpc.Durability.Temporary)
167
- // throw true
168
- } catch {
169
- this.keyExpired = true
170
- }
171
- }
172
160
  }
173
161
 
174
162
  if (!contractId)
@@ -222,7 +210,7 @@ export class PasskeyKit extends PasskeyBase {
222
210
  allowCredentials: [
223
211
  {
224
212
  id: keyId instanceof Uint8Array
225
- ? base64url(keyId)
213
+ ? base64url(Buffer.from(keyId))
226
214
  : keyId || this.keyId!,
227
215
  type: "public-key",
228
216
  },
@@ -231,8 +219,9 @@ export class PasskeyKit extends PasskeyBase {
231
219
  }
232
220
  );
233
221
 
234
- const signatureRaw = base64url.toBuffer(authenticationResponse.response.signature);
235
- const signature = this.convertEcdsaSignatureAsnToCompact(signatureRaw);
222
+ const signature = this.compactSignature(
223
+ base64url.toBuffer(authenticationResponse.response.signature)
224
+ );
236
225
 
237
226
  credentials.signatureExpirationLedger(lastLedger + ledgersToLive)
238
227
  credentials.signature(xdr.ScVal.scvMap([
@@ -256,6 +245,7 @@ export class PasskeyKit extends PasskeyBase {
256
245
 
257
246
  return entry
258
247
  }
248
+
259
249
  public async signAuthEntries(
260
250
  entries: xdr.SorobanAuthorizationEntry[],
261
251
  options?: {
@@ -276,6 +266,7 @@ export class PasskeyKit extends PasskeyBase {
276
266
 
277
267
  return entries
278
268
  }
269
+
279
270
  public async sign(
280
271
  txn: Transaction | string,
281
272
  options?: {
@@ -323,27 +314,6 @@ export class PasskeyKit extends PasskeyBase {
323
314
 
324
315
  return SorobanRpc.assembleTransaction(txn, sim).build().toXDR()
325
316
  }
326
- public async getSuperKeyId() {
327
- const data: Map<string, any> = new Map()
328
-
329
- const { val } = await this.rpc.getContractData(
330
- this.wallet!.options.contractId,
331
- xdr.ScVal.scvLedgerKeyContractInstance(),
332
- );
333
-
334
- val.contractData()
335
- .val()
336
- .instance()
337
- .storage()
338
- ?.forEach((entry) => {
339
- data.set(
340
- scValToNative(entry.key()),
341
- scValToNative(entry.val()),
342
- );
343
- });
344
-
345
- return base64url(data.get('super'))
346
- }
347
317
 
348
318
  /* TODO
349
319
  - Add a getKeyInfo action to get info about a specific passkey
@@ -351,133 +321,67 @@ export class PasskeyKit extends PasskeyBase {
351
321
  @Later
352
322
  */
353
323
 
354
- private getPublicKeyObject(attestationObject: string) {
355
- const { authData } = decode(base64url.toBuffer(attestationObject));
356
- const authDataUint8Array = new Uint8Array(authData);
357
- const authDataView = new DataView(authDataUint8Array.buffer, 0, authDataUint8Array.length);
358
-
359
- let offset = 0;
360
-
361
- // RP ID Hash (32 bytes)
362
- const rpIdHash = authData.slice(offset, offset + 32);
363
- offset += 32;
364
-
365
- // Flags (1 byte)
366
- const flags = authDataView.getUint8(offset);
367
- offset += 1;
368
-
369
- // Sign Count (4 bytes, big endian)
370
- const signCount = authDataView.getUint32(offset, false);
371
- offset += 4;
372
-
373
- // Attested Credential Data, if present
374
- if (flags & 0x40) { // Checking the AT flag
375
- // AAGUID (16 bytes)
376
- const aaguid = authData.slice(offset, offset + 16);
377
- offset += 16;
378
-
379
- // Credential ID Length (2 bytes, big endian)
380
- const credIdLength = authDataView.getUint16(offset, false);
381
- offset += 2;
382
-
383
- // Credential ID (variable length)
384
- const credentialId = authData.slice(offset, offset + credIdLength);
385
- offset += credIdLength;
386
-
387
- // Credential Public Key - (77 bytes...I hope)
388
- const credentialPublicKey = authData.slice(offset, offset + 77);
389
- offset += 77;
390
-
391
- // Any leftover bytes. I found some when using a YubiKey
392
- const theRest = authData.slice(offset);
393
-
394
- // Decode the credential public key to COSE
395
- const publicKeyObject = new Map<string, any>(Object.entries(decode(credentialPublicKey)));
396
-
397
- return {
398
- rpIdHash,
399
- flags,
400
- signCount,
401
- aaguid,
402
- credIdLength,
403
- credentialId,
404
- credentialPublicKey,
405
- theRest,
406
- publicKeyObject
407
- };
408
- }
409
-
410
- throw new Error("Attested credential data not present in the flags.");
411
- }
324
+ private getPublicKey(response: AuthenticatorAttestationResponseJSON) {
325
+ let publicKey: Buffer | undefined
412
326
 
413
- private convertEcdsaSignatureAsnToCompact(sig: Buffer) {
414
- // Define the order of the curve secp256k1
415
- // https://github.com/RustCrypto/elliptic-curves/blob/master/p256/src/lib.rs#L72
416
- const q = Buffer.from('ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551', 'hex')
327
+ if (response.publicKey)
328
+ publicKey = base64url.toBuffer(response.publicKey).slice(response.publicKey.length - 65)
417
329
 
418
- // ASN Sequence
419
- let offset = 0;
420
- if (sig[offset] != 0x30) {
421
- throw "signature is not a sequence";
330
+ if (
331
+ !publicKey
332
+ || publicKey[0] !== 0x04
333
+ || publicKey.length !== 65
334
+ ) {
335
+ // Extract and decode attestation object (CBOR), slice authenticator data to get COSE-encoded public key, and decode COSE to obtain key components
336
+ const { authData } = response.authenticatorData ? { authData: base64url.toBuffer(response.authenticatorData) } : decode(base64url.toBuffer(response.attestationObject));
337
+ const credentialIdLength = (authData[53] << 8) + authData[54];
338
+ const publicKeyBytes = authData.slice(55 + credentialIdLength);
339
+ const publicKeyCose = decode(publicKeyBytes);
340
+
341
+ publicKey = Buffer.from([
342
+ 0x04, // (0x04 prefix) https://en.bitcoin.it/wiki/Elliptic_Curve_Digital_Signature_Algorithm
343
+ ...publicKeyCose['-2'],
344
+ ...publicKeyCose['-3']
345
+ ])
422
346
  }
423
- offset += 1;
424
347
 
425
- // ASN Sequence Byte Length
426
- offset += 1;
427
-
428
- // ASN Integer (R)
429
- if (sig[offset] != 0x02) {
430
- throw "first element in sequence is not an integer";
431
- }
432
- offset += 1;
348
+ return publicKey
349
+ }
433
350
 
434
- // ASN Integer (R) Byte Length
435
- const rLen = sig[offset];
436
- offset += 1;
351
+ private compactSignature(signature: Buffer) {
352
+ // Decode the DER signature
353
+ let offset = 2;
437
354
 
438
- // ASN Integer (R) Byte Value
439
- if (rLen >= 33) {
440
- if (rLen != 33 || sig[offset] != 0x00) {
441
- throw "can only handle larger than 32 byte R's that are len 33 and lead with zero";
442
- }
443
- offset += 1;
444
- }
445
- const r = sig.slice(offset, offset + 32);
446
- offset += 32;
355
+ const rLength = signature[offset + 1];
356
+ const r = signature.slice(offset + 2, offset + 2 + rLength);
447
357
 
448
- // ASN Integer (S)
449
- if (sig[offset] != 0x02) {
450
- throw "second element in sequence is not an integer";
451
- }
452
- offset += 1;
358
+ offset += 2 + rLength;
453
359
 
454
- // ASN Integer (S) Byte Length
455
- const sLen = sig[offset];
456
- offset += 1;
360
+ const sLength = signature[offset + 1];
361
+ const s = signature.slice(offset + 2, offset + 2 + sLength);
457
362
 
458
- // ASN Integer (S) Byte Value
459
- if (sLen >= 33) {
460
- if (sLen != 33 || sig[offset] != 0x00) {
461
- throw "can only handle larger than 32 byte R's that are len 33 and lead with zero";
462
- }
463
- offset += 1;
464
- }
363
+ // Convert r and s to BigInt
364
+ const rBigInt = BigInt('0x' + r.toString('hex'));
365
+ let sBigInt = BigInt('0x' + s.toString('hex'));
465
366
 
466
- const s = sig.slice(offset, offset + 32);
367
+ // Ensure s is in the low-S form
368
+ // https://github.com/stellar/stellar-protocol/discussions/1435#discussioncomment-8809175
369
+ // https://discord.com/channels/897514728459468821/1233048618571927693
370
+ // Define the order of the curve secp256r1
371
+ // https://github.com/RustCrypto/elliptic-curves/blob/master/p256/src/lib.rs#L72
372
+ const n = BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551');
373
+ const halfN = n / 2n;
467
374
 
468
- offset += 32;
375
+ if (sBigInt > halfN)
376
+ sBigInt = n - sBigInt;
469
377
 
470
- let signature64: Buffer
378
+ // Convert back to buffers and ensure they are 32 bytes
379
+ const rPadded = Buffer.from(rBigInt.toString(16).padStart(64, '0'), 'hex');
380
+ const sLowS = Buffer.from(sBigInt.toString(16).padStart(64, '0'), 'hex');
471
381
 
472
- // Force low S range
473
- // https://github.com/stellar/stellar-protocol/discussions/1435#discussioncomment-8809175
474
- // https://discord.com/channels/897514728459468821/1233048618571927693
475
- if (bufToBigint(s) > ((bufToBigint(q) - BigInt(1)) / BigInt(2))) {
476
- signature64 = Buffer.from([...r, ...Buffer.from(bigintToBuf(bufToBigint(q) - bufToBigint(s), true) as ArrayBuffer)]);
477
- } else {
478
- signature64 = Buffer.from([...r, ...s]);
479
- }
382
+ // Concatenate r and low-s
383
+ const concatSignature = Buffer.concat([rPadded, sLowS]);
480
384
 
481
- return signature64;
385
+ return concatSignature;
482
386
  }
483
387
  }
package/tsconfig.json CHANGED
@@ -6,7 +6,6 @@
6
6
  "target": "ESNext",
7
7
  "module": "ESNext",
8
8
  "moduleDetection": "force",
9
- // "allowJs": true,
10
9
 
11
10
  // Bundler mode
12
11
  "moduleResolution": "node",
@@ -26,5 +25,5 @@
26
25
  "noPropertyAccessFromIndexSignature": false,
27
26
  "strictNullChecks": true,
28
27
  },
29
- "exclude": ["./demo", "./packages", "./types", "./contracts", "./zephyr"]
28
+ "exclude": ["./demo", "./packages", "./types", "./contracts", "./zephyr", "./bun_tests"]
30
29
  }