@1shotapi/ows-signer 0.2.0 → 0.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1shotapi/ows-signer",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "OWS Signing Layer — plain JavaScript WebAuthn PRF key custody with curve-based signing (on-chain via EIP-8244)",
5
5
  "type": "module",
6
6
  "files": [
@@ -27,10 +27,12 @@ export async function signWithScheme(
27
27
  lowS: true,
28
28
  });
29
29
  const compact = sig.toCompactRawBytes();
30
+ // Ethereum ecrecover / OpenZeppelin ECDSA require v ∈ {27, 28}.
31
+ // Noble returns recovery ∈ {0,1} (rarely 2,3); mask to yParity then +27.
30
32
  const recovery = sig.recovery ?? 0;
31
33
  const out = new Uint8Array(65);
32
34
  out.set(compact, 0);
33
- out[64] = recovery;
35
+ out[64] = 27 + (recovery & 1);
34
36
  return to0xHex(out);
35
37
  }
36
38
  case "secp256k1-bip340":
package/src/handlers.js CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  getAssertionSignatureBase64Url,
37
37
  getCredentialId,
38
38
  getPasskeyAssertion,
39
- getPasskeyPublicKeyBase64Url,
39
+ getCosePublicKeyBase64Url,
40
40
  } from "./webauthn.js";
41
41
 
42
42
  /**
@@ -227,7 +227,7 @@ async function handleCreateCredential(params, correlationId, targetOrigin) {
227
227
 
228
228
  emitEvent(window.parent, targetOrigin, "CredentialCreated", correlationId, {
229
229
  credentialId,
230
- passkeyPublicKey: getPasskeyPublicKeyBase64Url(credential),
230
+ cosePublicKey: getCosePublicKeyBase64Url(credential),
231
231
  secp256k1PublicKey: to0xHex(keys.secp256k1PublicKey),
232
232
  });
233
233
  zeroize(keys.secp256k1PrivateKey);
@@ -626,7 +626,7 @@ async function handleGetPublicKey(params, correlationId, targetOrigin) {
626
626
 
627
627
  emitEvent(window.parent, targetOrigin, "PublicKey", correlationId, {
628
628
  credentialId: getCredentialId(credential),
629
- passkeyPublicKey: getPasskeyPublicKeyBase64Url(credential),
629
+ cosePublicKey: getCosePublicKeyBase64Url(credential),
630
630
  secp256k1PublicKey: to0xHex(keys.secp256k1PublicKey),
631
631
  ed25519PublicKey: to0xHex(keys.ed25519PublicKey),
632
632
  });
package/src/webauthn.js CHANGED
@@ -126,15 +126,112 @@ export function getCredentialId(credential) {
126
126
 
127
127
  /**
128
128
  * @param {PublicKeyCredential} credential
129
- * @returns {string | null}
129
+ * @returns {string | null} COSE credentialPublicKey as base64url, or null
130
130
  */
131
- export function getPasskeyPublicKeyBase64Url(credential) {
131
+ export function getCosePublicKeyBase64Url(credential) {
132
132
  const response = credential.response;
133
- if ("getPublicKey" in response && typeof response.getPublicKey === "function") {
134
- const key = response.getPublicKey();
135
- return key ? bufferToBase64Url(key) : null;
133
+ // COSE lives in attestation authenticator data only — not on assertions,
134
+ // and not via AuthenticatorAttestationResponse.getPublicKey() (that is SPKI).
135
+ if (
136
+ !("getAuthenticatorData" in response) ||
137
+ typeof response.getAuthenticatorData !== "function"
138
+ ) {
139
+ return null;
136
140
  }
137
- return null;
141
+ const authData = response.getAuthenticatorData();
142
+ if (!authData) return null;
143
+ const cose = extractCosePublicKeyFromAuthenticatorData(
144
+ new Uint8Array(authData),
145
+ );
146
+ return cose ? bufferToBase64Url(cose) : null;
147
+ }
148
+
149
+ /**
150
+ * Slice the COSE credentialPublicKey from attested credential data.
151
+ * @param {Uint8Array} authData
152
+ * @returns {Uint8Array | null}
153
+ */
154
+ function extractCosePublicKeyFromAuthenticatorData(authData) {
155
+ if (authData.length < 37) return null;
156
+ const flags = authData[32];
157
+ const hasAttestedCredentialData = (flags & 0x40) !== 0;
158
+ if (!hasAttestedCredentialData) return null;
159
+
160
+ let offset = 37; // rpIdHash(32) + flags(1) + signCount(4)
161
+ offset += 16; // aaguid
162
+ if (offset + 2 > authData.length) return null;
163
+ const credentialIdLength = (authData[offset] << 8) | authData[offset + 1];
164
+ offset += 2 + credentialIdLength;
165
+ if (offset >= authData.length) return null;
166
+
167
+ try {
168
+ const length = cborFirstItemLength(authData, offset);
169
+ return authData.subarray(offset, offset + length);
170
+ } catch {
171
+ return null;
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Byte length of the first definite-length CBOR item at `offset`.
177
+ * Sufficient for WebAuthn COSE public keys (maps of ints / bstrs).
178
+ * @param {Uint8Array} buf
179
+ * @param {number} offset
180
+ * @returns {number}
181
+ */
182
+ function cborFirstItemLength(buf, offset) {
183
+ if (offset >= buf.length) throw new Error("cborTruncated");
184
+ const initial = buf[offset];
185
+ const major = initial >> 5;
186
+ const additional = initial & 31;
187
+ let pos = offset + 1;
188
+ let argument = additional;
189
+
190
+ if (additional === 24) {
191
+ argument = buf[pos];
192
+ pos += 1;
193
+ } else if (additional === 25) {
194
+ argument = (buf[pos] << 8) | buf[pos + 1];
195
+ pos += 2;
196
+ } else if (additional === 26) {
197
+ argument =
198
+ (buf[pos] << 24) |
199
+ (buf[pos + 1] << 16) |
200
+ (buf[pos + 2] << 8) |
201
+ buf[pos + 3];
202
+ pos += 4;
203
+ } else if (additional === 27) {
204
+ // 64-bit; WebAuthn keys never need this size
205
+ throw new Error("cborUint64Unsupported");
206
+ } else if (additional >= 28) {
207
+ throw new Error("cborIndefiniteUnsupported");
208
+ }
209
+
210
+ if (major === 0 || major === 1) {
211
+ return pos - offset;
212
+ }
213
+ if (major === 2 || major === 3) {
214
+ return pos - offset + argument;
215
+ }
216
+ if (major === 7) {
217
+ if (additional < 24) return pos - offset;
218
+ if (additional === 25) return pos - offset + 2;
219
+ if (additional === 26) return pos - offset + 4;
220
+ if (additional === 27) return pos - offset + 8;
221
+ return pos - offset;
222
+ }
223
+ if (major === 6) {
224
+ const inner = cborFirstItemLength(buf, pos);
225
+ return pos - offset + inner;
226
+ }
227
+ if (major === 4 || major === 5) {
228
+ const valueCount = major === 5 ? argument * 2 : argument;
229
+ for (let i = 0; i < valueCount; i++) {
230
+ pos += cborFirstItemLength(buf, pos);
231
+ }
232
+ return pos - offset;
233
+ }
234
+ throw new Error("cborUnsupportedMajor");
138
235
  }
139
236
 
140
237
  /**