UNPKG

@mysten/sui

Version:
1 lines 7.75 kB
{"version":3,"file":"publickey.mjs","names":["secp256r1"],"sources":["../../../src/keypairs/passkey/publickey.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n\nimport { fromBase64, toBase64 } from '@mysten/bcs';\nimport { p256 as secp256r1 } from '@noble/curves/nist.js';\nimport { sha256 } from '@noble/hashes/sha2.js';\n\nimport { PasskeyAuthenticator } from '../../bcs/bcs.js';\nimport { bytesEqual, PublicKey } from '../../cryptography/publickey.js';\nimport type { PublicKeyInitData } from '../../cryptography/publickey.js';\nimport { SIGNATURE_SCHEME_TO_FLAG } from '../../cryptography/signature-scheme.js';\n\nexport const PASSKEY_PUBLIC_KEY_SIZE = 33;\nexport const PASSKEY_UNCOMPRESSED_PUBLIC_KEY_SIZE = 65;\nexport const PASSKEY_SIGNATURE_SIZE = 64;\n/** Fixed DER header for secp256r1 SubjectPublicKeyInfo\nDER structure for P-256 SPKI:\n30 -- SEQUENCE\n 59 -- length (89 bytes)\n 30 -- SEQUENCE\n 13 -- length (19 bytes)\n 06 -- OBJECT IDENTIFIER\n 07 -- length\n 2A 86 48 CE 3D 02 01 -- id-ecPublicKey\n 06 -- OBJECT IDENTIFIER\n 08 -- length\n 2A 86 48 CE 3D 03 01 07 -- secp256r1/prime256v1\n 03 -- BIT STRING\n 42 -- length (66 bytes)\n 00 -- padding\n\t===== above bytes are considered header =====\n 04 || x || y -- uncompressed point (65 bytes: 0x04 || 32-byte x || 32-byte y)\n*/\nexport const SECP256R1_SPKI_HEADER = new Uint8Array([\n\t0x30,\n\t0x59, // SEQUENCE, length 89\n\t0x30,\n\t0x13, // SEQUENCE, length 19\n\t0x06,\n\t0x07, // OID, length 7\n\t0x2a,\n\t0x86,\n\t0x48,\n\t0xce,\n\t0x3d,\n\t0x02,\n\t0x01, // OID: 1.2.840.10045.2.1 (ecPublicKey)\n\t0x06,\n\t0x08, // OID, length 8\n\t0x2a,\n\t0x86,\n\t0x48,\n\t0xce,\n\t0x3d,\n\t0x03,\n\t0x01,\n\t0x07, // OID: 1.2.840.10045.3.1.7 (prime256v1/secp256r1)\n\t0x03,\n\t0x42, // BIT STRING, length 66\n\t0x00, // no unused bits\n] as const);\n\n/**\n * A passkey public key\n */\nexport class PasskeyPublicKey extends PublicKey {\n\tstatic SIZE = PASSKEY_PUBLIC_KEY_SIZE;\n\tprivate data: Uint8Array<ArrayBuffer>;\n\n\t/**\n\t * Create a new PasskeyPublicKey object\n\t * @param value passkey public key as buffer or base-64 encoded string\n\t */\n\tconstructor(value: PublicKeyInitData) {\n\t\tsuper();\n\n\t\tif (typeof value === 'string') {\n\t\t\tthis.data = fromBase64(value);\n\t\t} else if (value instanceof Uint8Array) {\n\t\t\tthis.data = value as Uint8Array<ArrayBuffer>;\n\t\t} else {\n\t\t\tthis.data = Uint8Array.from(value);\n\t\t}\n\n\t\tif (this.data.length !== PASSKEY_PUBLIC_KEY_SIZE) {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid public key input. Expected ${PASSKEY_PUBLIC_KEY_SIZE} bytes, got ${this.data.length}`,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Checks if two passkey public keys are equal\n\t */\n\toverride equals(publicKey: PasskeyPublicKey): boolean {\n\t\treturn super.equals(publicKey);\n\t}\n\n\t/**\n\t * Return the byte array representation of the Secp256r1 public key\n\t */\n\ttoRawBytes(): Uint8Array<ArrayBuffer> {\n\t\treturn this.data;\n\t}\n\n\t/**\n\t * Return the Sui address associated with this Secp256r1 public key\n\t */\n\tflag(): number {\n\t\treturn SIGNATURE_SCHEME_TO_FLAG['Passkey'];\n\t}\n\n\t/**\n\t * Verifies that the signature is valid for for the provided message\n\t */\n\tasync verify(message: Uint8Array, signature: Uint8Array | string): Promise<boolean> {\n\t\tconst parsed = parseSerializedPasskeySignature(signature);\n\t\tconst clientDataJSON = JSON.parse(parsed.clientDataJson);\n\n\t\tif (clientDataJSON.type !== 'webauthn.get') {\n\t\t\treturn false;\n\t\t}\n\n\t\t// parse challenge from base64 url\n\t\tconst parsedChallenge = fromBase64(\n\t\t\tclientDataJSON.challenge.replace(/-/g, '+').replace(/_/g, '/'),\n\t\t);\n\t\tif (!bytesEqual(message, parsedChallenge)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst pk = parsed.userSignature.slice(1 + PASSKEY_SIGNATURE_SIZE);\n\t\tif (!bytesEqual(this.toRawBytes(), pk)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst payload = new Uint8Array([\n\t\t\t...parsed.authenticatorData,\n\t\t\t...sha256(new TextEncoder().encode(parsed.clientDataJson)),\n\t\t]);\n\t\tconst sig = parsed.userSignature.slice(1, PASSKEY_SIGNATURE_SIZE + 1);\n\t\treturn secp256r1.verify(sig, payload, pk);\n\t}\n}\n\n/**\n * Parses a DER SubjectPublicKeyInfo into an uncompressed public key. This also verifies\n * that the curve used is P-256 (secp256r1).\n *\n * @param data: DER SubjectPublicKeyInfo\n * @returns uncompressed public key (`0x04 || x || y`)\n */\nexport function parseDerSPKI(derBytes: Uint8Array): Uint8Array {\n\t// Verify length and header bytes are expected\n\tif (derBytes.length !== SECP256R1_SPKI_HEADER.length + PASSKEY_UNCOMPRESSED_PUBLIC_KEY_SIZE) {\n\t\tthrow new Error('Invalid DER length');\n\t}\n\tfor (let i = 0; i < SECP256R1_SPKI_HEADER.length; i++) {\n\t\tif (derBytes[i] !== SECP256R1_SPKI_HEADER[i]) {\n\t\t\tthrow new Error('Invalid spki header');\n\t\t}\n\t}\n\n\tif (derBytes[SECP256R1_SPKI_HEADER.length] !== 0x04) {\n\t\tthrow new Error('Invalid point marker');\n\t}\n\n\t// Returns the last 65 bytes `04 || x || y`\n\treturn derBytes.slice(SECP256R1_SPKI_HEADER.length);\n}\n\n/**\n * Parse signature from bytes or base64 string into the following fields.\n */\nexport function parseSerializedPasskeySignature(signature: Uint8Array | string) {\n\tconst bytes = typeof signature === 'string' ? fromBase64(signature) : signature;\n\n\tif (bytes[0] !== SIGNATURE_SCHEME_TO_FLAG.Passkey) {\n\t\tthrow new Error('Invalid signature scheme');\n\t}\n\tconst dec = PasskeyAuthenticator.parse(bytes.slice(1));\n\treturn {\n\t\tsignatureScheme: 'Passkey' as const,\n\t\tserializedSignature: toBase64(bytes),\n\t\tsignature: bytes,\n\t\tauthenticatorData: dec.authenticatorData,\n\t\tclientDataJson: dec.clientDataJson,\n\t\tuserSignature: new Uint8Array(dec.userSignature),\n\t\tpublicKey: new Uint8Array(dec.userSignature.slice(1 + PASSKEY_SIGNATURE_SIZE)),\n\t};\n}\n"],"mappings":";;;;;;;;AAYA,MAAa,0BAA0B;AACvC,MAAa,uCAAuC;AACpD,MAAa,yBAAyB;;;;;;;;;;;;;;;;;;;AAmBtC,MAAa,wBAAwB,IAAI,WAAW;CACnD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,CAAU;;;;AAKX,IAAa,mBAAb,cAAsC,UAAU;;cACjC;;;;;;CAOd,YAAY,OAA0B;AACrC,SAAO;AAEP,MAAI,OAAO,UAAU,SACpB,MAAK,OAAO,WAAW,MAAM;WACnB,iBAAiB,WAC3B,MAAK,OAAO;MAEZ,MAAK,OAAO,WAAW,KAAK,MAAM;AAGnC,MAAI,KAAK,KAAK,WAAW,wBACxB,OAAM,IAAI,MACT,sCAAsC,wBAAwB,cAAc,KAAK,KAAK,SACtF;;;;;CAOH,AAAS,OAAO,WAAsC;AACrD,SAAO,MAAM,OAAO,UAAU;;;;;CAM/B,aAAsC;AACrC,SAAO,KAAK;;;;;CAMb,OAAe;AACd,SAAO,yBAAyB;;;;;CAMjC,MAAM,OAAO,SAAqB,WAAkD;EACnF,MAAM,SAAS,gCAAgC,UAAU;EACzD,MAAM,iBAAiB,KAAK,MAAM,OAAO,eAAe;AAExD,MAAI,eAAe,SAAS,eAC3B,QAAO;AAOR,MAAI,CAAC,WAAW,SAHQ,WACvB,eAAe,UAAU,QAAQ,MAAM,IAAI,CAAC,QAAQ,MAAM,IAAI,CAC9D,CACwC,CACxC,QAAO;EAGR,MAAM,KAAK,OAAO,cAAc,MAAM,IAAI,uBAAuB;AACjE,MAAI,CAAC,WAAW,KAAK,YAAY,EAAE,GAAG,CACrC,QAAO;EAGR,MAAM,UAAU,IAAI,WAAW,CAC9B,GAAG,OAAO,mBACV,GAAG,OAAO,IAAI,aAAa,CAAC,OAAO,OAAO,eAAe,CAAC,CAC1D,CAAC;EACF,MAAM,MAAM,OAAO,cAAc,MAAM,GAAG,yBAAyB,EAAE;AACrE,SAAOA,KAAU,OAAO,KAAK,SAAS,GAAG;;;;;;;;;;AAW3C,SAAgB,aAAa,UAAkC;AAE9D,KAAI,SAAS,WAAW,sBAAsB,SAAS,qCACtD,OAAM,IAAI,MAAM,qBAAqB;AAEtC,MAAK,IAAI,IAAI,GAAG,IAAI,sBAAsB,QAAQ,IACjD,KAAI,SAAS,OAAO,sBAAsB,GACzC,OAAM,IAAI,MAAM,sBAAsB;AAIxC,KAAI,SAAS,sBAAsB,YAAY,EAC9C,OAAM,IAAI,MAAM,uBAAuB;AAIxC,QAAO,SAAS,MAAM,sBAAsB,OAAO;;;;;AAMpD,SAAgB,gCAAgC,WAAgC;CAC/E,MAAM,QAAQ,OAAO,cAAc,WAAW,WAAW,UAAU,GAAG;AAEtE,KAAI,MAAM,OAAO,yBAAyB,QACzC,OAAM,IAAI,MAAM,2BAA2B;CAE5C,MAAM,MAAM,qBAAqB,MAAM,MAAM,MAAM,EAAE,CAAC;AACtD,QAAO;EACN,iBAAiB;EACjB,qBAAqB,SAAS,MAAM;EACpC,WAAW;EACX,mBAAmB,IAAI;EACvB,gBAAgB,IAAI;EACpB,eAAe,IAAI,WAAW,IAAI,cAAc;EAChD,WAAW,IAAI,WAAW,IAAI,cAAc,MAAM,IAAI,uBAAuB,CAAC;EAC9E"}