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.
- package/PROPOSAL.md +397 -0
- package/README.md +4 -2
- package/cheatsheet +5 -4
- package/package.json +4 -4
- package/packages/passkey-factory-sdk/package.json +3 -3
- package/packages/passkey-factory-sdk/src/index.ts +5 -5
- package/packages/passkey-factory-sdk/types/index.d.ts +3 -2
- package/packages/passkey-kit-sdk/package.json +3 -3
- package/packages/passkey-kit-sdk/src/index.ts +21 -68
- package/packages/passkey-kit-sdk/types/index.d.ts +12 -61
- package/src/kit.ts +81 -177
- package/tsconfig.json +1 -2
- package/types/kit.d.ts +5 -7
- package/types/src/base.d.ts +9 -0
- package/types/src/index.d.ts +3 -0
- package/types/src/kit.d.ts +53 -0
- package/types/tests/index.d.ts +1 -0
- package/TODO.md +0 -1
|
@@ -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: "
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(["
|
|
147
|
-
"
|
|
148
|
-
"
|
|
149
|
-
"
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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: "
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
111
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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,
|
|
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(
|
|
53
|
-
const { keyId, publicKey } = await this.createKey(
|
|
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(
|
|
76
|
-
const
|
|
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:
|
|
85
|
-
displayName
|
|
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 =
|
|
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(
|
|
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:
|
|
112
|
+
let keyIdBuffer: Buffer
|
|
119
113
|
|
|
120
114
|
if (!keyId) {
|
|
121
|
-
const
|
|
115
|
+
const response = await startAuthentication({
|
|
122
116
|
challenge: base64url("stellaristhebetterblockchain"),
|
|
123
117
|
// rpId: undefined,
|
|
124
118
|
userVerification: "discouraged",
|
|
125
119
|
});
|
|
126
120
|
|
|
127
|
-
|
|
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(
|
|
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
|
|
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
|
|
235
|
-
|
|
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
|
|
355
|
-
|
|
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
|
-
|
|
414
|
-
|
|
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
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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
|
-
|
|
426
|
-
|
|
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
|
-
|
|
435
|
-
|
|
436
|
-
offset
|
|
351
|
+
private compactSignature(signature: Buffer) {
|
|
352
|
+
// Decode the DER signature
|
|
353
|
+
let offset = 2;
|
|
437
354
|
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
|
|
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
|
-
|
|
455
|
-
const
|
|
456
|
-
offset += 1;
|
|
360
|
+
const sLength = signature[offset + 1];
|
|
361
|
+
const s = signature.slice(offset + 2, offset + 2 + sLength);
|
|
457
362
|
|
|
458
|
-
//
|
|
459
|
-
|
|
460
|
-
|
|
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
|
-
|
|
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
|
-
|
|
375
|
+
if (sBigInt > halfN)
|
|
376
|
+
sBigInt = n - sBigInt;
|
|
469
377
|
|
|
470
|
-
|
|
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
|
-
//
|
|
473
|
-
|
|
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
|
|
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
|
}
|