@mysten/sui
Version:
Sui TypeScript API(Work in Progress)
252 lines (251 loc) • 10.7 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __typeError = (msg) => {
throw TypeError(msg);
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
var keypair_exports = {};
__export(keypair_exports, {
BrowserPasskeyProvider: () => BrowserPasskeyProvider,
PasskeyKeypair: () => PasskeyKeypair,
findCommonPublicKey: () => findCommonPublicKey
});
module.exports = __toCommonJS(keypair_exports);
var import_bcs = require("@mysten/bcs");
var import_p256 = require("@noble/curves/p256");
var import_blake2b = require("@noble/hashes/blake2b");
var import_sha256 = require("@noble/hashes/sha256");
var import_utils = require("@noble/hashes/utils");
var import_bcs2 = require("../../bcs/bcs.js");
var import_cryptography = require("../../cryptography/index.js");
var import_publickey = require("./publickey.js");
var _name, _options;
class BrowserPasskeyProvider {
constructor(name, options) {
__privateAdd(this, _name);
__privateAdd(this, _options);
__privateSet(this, _name, name);
__privateSet(this, _options, options);
}
async create() {
return await navigator.credentials.create({
publicKey: {
timeout: __privateGet(this, _options).timeout ?? 6e4,
...__privateGet(this, _options),
rp: {
name: __privateGet(this, _name),
...__privateGet(this, _options).rp
},
user: {
name: __privateGet(this, _name),
displayName: __privateGet(this, _name),
...__privateGet(this, _options).user,
id: (0, import_utils.randomBytes)(10)
},
challenge: new TextEncoder().encode("Create passkey wallet on Sui"),
pubKeyCredParams: [{ alg: -7, type: "public-key" }],
authenticatorSelection: {
authenticatorAttachment: "cross-platform",
residentKey: "required",
requireResidentKey: true,
userVerification: "required",
...__privateGet(this, _options).authenticatorSelection
}
}
});
}
async get(challenge) {
return await navigator.credentials.get({
publicKey: {
challenge,
userVerification: __privateGet(this, _options).authenticatorSelection?.userVerification || "required",
timeout: __privateGet(this, _options).timeout ?? 6e4
}
});
}
}
_name = new WeakMap();
_options = new WeakMap();
class PasskeyKeypair extends import_cryptography.Signer {
/**
* Get the key scheme of passkey,
*/
getKeyScheme() {
return "Passkey";
}
/**
* Creates an instance of Passkey signer. If no passkey wallet had created before,
* use `getPasskeyInstance`. For example:
* ```
* let provider = new BrowserPasskeyProvider('Sui Passkey Example',{
* rpName: 'Sui Passkey Example',
* rpId: window.location.hostname,
* } as BrowserPasswordProviderOptions);
* const signer = await PasskeyKeypair.getPasskeyInstance(provider);
* ```
*
* If there are existing passkey wallet, use `signAndRecover` to identify the correct
* public key and then initialize the instance. See usage in `signAndRecover`.
*/
constructor(publicKey, provider) {
super();
this.publicKey = publicKey;
this.provider = provider;
}
/**
* Creates an instance of Passkey signer invoking the passkey from navigator.
* Note that this will invoke the passkey device to create a fresh credential.
* Should only be called if passkey wallet is created for the first time.
*
* @param provider - the passkey provider.
* @returns the passkey instance.
*/
static async getPasskeyInstance(provider) {
const credential = await provider.create();
if (!credential.response.getPublicKey()) {
throw new Error("Invalid credential create response");
} else {
const derSPKI = credential.response.getPublicKey();
const pubkeyUncompressed = (0, import_publickey.parseDerSPKI)(new Uint8Array(derSPKI));
const pubkey = import_p256.secp256r1.ProjectivePoint.fromHex(pubkeyUncompressed);
const pubkeyCompressed = pubkey.toRawBytes(true);
return new PasskeyKeypair(pubkeyCompressed, provider);
}
}
/**
* Return the public key for this passkey.
*/
getPublicKey() {
return new import_publickey.PasskeyPublicKey(this.publicKey);
}
/**
* Return the signature for the provided data (i.e. blake2b(intent_message)).
* This is sent to passkey as the challenge field.
*/
async sign(data) {
const credential = await this.provider.get(data);
const authenticatorData = new Uint8Array(credential.response.authenticatorData);
const clientDataJSON = new Uint8Array(credential.response.clientDataJSON);
const decoder = new TextDecoder();
const clientDataJSONString = decoder.decode(clientDataJSON);
const sig = import_p256.secp256r1.Signature.fromDER(new Uint8Array(credential.response.signature));
const normalized = sig.normalizeS().toCompactRawBytes();
if (normalized.length !== import_publickey.PASSKEY_SIGNATURE_SIZE || this.publicKey.length !== import_publickey.PASSKEY_PUBLIC_KEY_SIZE) {
throw new Error("Invalid signature or public key length");
}
const arr = new Uint8Array(1 + normalized.length + this.publicKey.length);
arr.set([import_cryptography.SIGNATURE_SCHEME_TO_FLAG["Secp256r1"]]);
arr.set(normalized, 1);
arr.set(this.publicKey, 1 + normalized.length);
return import_bcs2.PasskeyAuthenticator.serialize({
authenticatorData,
clientDataJson: clientDataJSONString,
userSignature: arr
}).toBytes();
}
/**
* This overrides the base class implementation that accepts the raw bytes and signs its
* digest of the intent message, then serialize it with the passkey flag.
*/
async signWithIntent(bytes, intent) {
const intentMessage = (0, import_cryptography.messageWithIntent)(intent, bytes);
const digest = (0, import_blake2b.blake2b)(intentMessage, { dkLen: 32 });
const signature = await this.sign(digest);
const serializedSignature = new Uint8Array(1 + signature.length);
serializedSignature.set([import_cryptography.SIGNATURE_SCHEME_TO_FLAG[this.getKeyScheme()]]);
serializedSignature.set(signature, 1);
return {
signature: (0, import_bcs.toBase64)(serializedSignature),
bytes: (0, import_bcs.toBase64)(bytes)
};
}
/**
* Given a message, asks the passkey device to sign it and return all (up to 4) possible public keys.
* See: https://bitcoin.stackexchange.com/questions/81232/how-is-public-key-extracted-from-message-digital-signature-address
*
* This is useful if the user previously created passkey wallet with the origin, but the wallet session
* does not have the public key / address. By calling this method twice with two different messages, the
* wallet can compare the returned public keys and uniquely identify the previously created passkey wallet
* using `findCommonPublicKey`.
*
* Alternatively, one call can be made and all possible public keys should be checked onchain to see if
* there is any assets.
*
* Once the correct public key is identified, a passkey instance can then be initialized with this public key.
*
* Example usage to recover wallet with two signing calls:
* ```
* let provider = new BrowserPasskeyProvider('Sui Passkey Example',{
* rpName: 'Sui Passkey Example',
* rpId: window.location.hostname,
* } as BrowserPasswordProviderOptions);
* const testMessage = new TextEncoder().encode('Hello world!');
* const possiblePks = await PasskeyKeypair.signAndRecover(provider, testMessage);
* const testMessage2 = new TextEncoder().encode('Hello world 2!');
* const possiblePks2 = await PasskeyKeypair.signAndRecover(provider, testMessage2);
* const commonPk = findCommonPublicKey(possiblePks, possiblePks2);
* const signer = new PasskeyKeypair(provider, commonPk.toRawBytes());
* ```
*
* @param provider - the passkey provider.
* @param message - the message to sign.
* @returns all possible public keys.
*/
static async signAndRecover(provider, message) {
const credential = await provider.get(message);
const fullMessage = messageFromAssertionResponse(credential.response);
const sig = import_p256.secp256r1.Signature.fromDER(new Uint8Array(credential.response.signature));
const res = [];
for (let i = 0; i < 4; i++) {
const s = sig.addRecoveryBit(i);
try {
const pubkey = s.recoverPublicKey((0, import_sha256.sha256)(fullMessage));
const pk = new import_publickey.PasskeyPublicKey(pubkey.toRawBytes(true));
res.push(pk);
} catch {
continue;
}
}
return res;
}
}
function findCommonPublicKey(arr1, arr2) {
const matchingPubkeys = [];
for (const pubkey1 of arr1) {
for (const pubkey2 of arr2) {
if (pubkey1.equals(pubkey2)) {
matchingPubkeys.push(pubkey1);
}
}
}
if (matchingPubkeys.length !== 1) {
throw new Error("No unique public key found");
}
return matchingPubkeys[0];
}
function messageFromAssertionResponse(response) {
const authenticatorData = new Uint8Array(response.authenticatorData);
const clientDataJSON = new Uint8Array(response.clientDataJSON);
const clientDataJSONDigest = (0, import_sha256.sha256)(clientDataJSON);
return new Uint8Array([...authenticatorData, ...clientDataJSONDigest]);
}
//# sourceMappingURL=keypair.js.map
;