UNPKG

ox

Version:

Ethereum Standard Library

460 lines 17.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.VerificationError = exports.InvalidSerializedError = exports.MissingPropertiesError = exports.CoercionError = exports.types = exports.magicBytes = void 0; exports.assert = assert; exports.deserialize = deserialize; exports.from = from; exports.fromRpc = fromRpc; exports.getType = getType; exports.serialize = serialize; exports.toRpc = toRpc; exports.validate = validate; exports.verify = verify; const Address = require("../core/Address.js"); const Errors = require("../core/Errors.js"); const Hex = require("../core/Hex.js"); const Json = require("../core/Json.js"); const ox_P256 = require("../core/P256.js"); const ox_Secp256k1 = require("../core/Secp256k1.js"); const Signature = require("../core/Signature.js"); const ox_WebAuthnP256 = require("../core/WebAuthnP256.js"); const serializedP256Type = '0x01'; const serializedWebAuthnType = '0x02'; const serializedKeychainType = '0x03'; exports.magicBytes = '0x7777777777777777777777777777777777777777777777777777777777777777'; exports.types = ['secp256k1', 'p256', 'webAuthn']; function assert(envelope) { const type = getType(envelope); if (type === 'secp256k1') { const secp256k1 = envelope; Signature.assert(secp256k1.signature); return; } if (type === 'p256') { const p256 = envelope; const missing = []; if (typeof p256.signature?.r !== 'bigint') missing.push('signature.r'); if (typeof p256.signature?.s !== 'bigint') missing.push('signature.s'); if (typeof p256.prehash !== 'boolean') missing.push('prehash'); if (!p256.publicKey) missing.push('publicKey'); else { if (typeof p256.publicKey.x !== 'bigint') missing.push('publicKey.x'); if (typeof p256.publicKey.y !== 'bigint') missing.push('publicKey.y'); } if (missing.length > 0) throw new MissingPropertiesError({ envelope, missing, type: 'p256' }); return; } if (type === 'webAuthn') { const webauthn = envelope; const missing = []; if (typeof webauthn.signature?.r !== 'bigint') missing.push('signature.r'); if (typeof webauthn.signature?.s !== 'bigint') missing.push('signature.s'); if (!webauthn.metadata) missing.push('metadata'); else { if (!webauthn.metadata.authenticatorData) missing.push('metadata.authenticatorData'); if (!webauthn.metadata.clientDataJSON) missing.push('metadata.clientDataJSON'); } if (!webauthn.publicKey) missing.push('publicKey'); else { if (typeof webauthn.publicKey.x !== 'bigint') missing.push('publicKey.x'); if (typeof webauthn.publicKey.y !== 'bigint') missing.push('publicKey.y'); } if (missing.length > 0) throw new MissingPropertiesError({ envelope, missing, type: 'webAuthn' }); return; } if (type === 'keychain') { const keychain = envelope; assert(keychain.inner); return; } } function deserialize(value) { const serialized = value.endsWith(exports.magicBytes.slice(2)) ? Hex.slice(value, 0, -Hex.size(exports.magicBytes)) : value; const size = Hex.size(serialized); if (size === 65) { const signature = Signature.fromHex(serialized); Signature.assert(signature); return { signature, type: 'secp256k1' }; } const typeId = Hex.slice(serialized, 0, 1); const data = Hex.slice(serialized, 1); const dataSize = Hex.size(data); if (typeId === serializedP256Type) { if (dataSize !== 129) throw new InvalidSerializedError({ reason: `Invalid P256 signature envelope size: expected 129 bytes, got ${dataSize} bytes`, serialized, }); return { publicKey: { prefix: 4, x: Hex.toBigInt(Hex.slice(data, 64, 96)), y: Hex.toBigInt(Hex.slice(data, 96, 128)), }, prehash: Hex.toNumber(Hex.slice(data, 128, 129)) !== 0, signature: { r: Hex.toBigInt(Hex.slice(data, 0, 32)), s: Hex.toBigInt(Hex.slice(data, 32, 64)), }, type: 'p256', }; } if (typeId === serializedWebAuthnType) { if (dataSize < 128) throw new InvalidSerializedError({ reason: `Invalid WebAuthn signature envelope size: expected at least 128 bytes, got ${dataSize} bytes`, serialized, }); const webauthnDataSize = dataSize - 128; const webauthnData = Hex.slice(data, 0, webauthnDataSize); let authenticatorData; let clientDataJSON; for (let split = 37; split < webauthnDataSize; split++) { const potentialJson = Hex.toString(Hex.slice(webauthnData, split)); if (potentialJson.startsWith('{') && potentialJson.endsWith('}')) { try { JSON.parse(potentialJson); authenticatorData = Hex.slice(webauthnData, 0, split); clientDataJSON = potentialJson; break; } catch { } } } if (!authenticatorData || !clientDataJSON) throw new InvalidSerializedError({ reason: 'Unable to parse WebAuthn metadata: could not extract valid authenticatorData and clientDataJSON', serialized, }); return { publicKey: { prefix: 4, x: Hex.toBigInt(Hex.slice(data, webauthnDataSize + 64, webauthnDataSize + 96)), y: Hex.toBigInt(Hex.slice(data, webauthnDataSize + 96, webauthnDataSize + 128)), }, metadata: { authenticatorData, clientDataJSON, }, signature: { r: Hex.toBigInt(Hex.slice(data, webauthnDataSize, webauthnDataSize + 32)), s: Hex.toBigInt(Hex.slice(data, webauthnDataSize + 32, webauthnDataSize + 64)), }, type: 'webAuthn', }; } if (typeId === serializedKeychainType) { const userAddress = Hex.slice(data, 0, 20); const inner = deserialize(Hex.slice(data, 20)); return { userAddress, inner, type: 'keychain', }; } throw new InvalidSerializedError({ reason: `Unknown signature type identifier: ${typeId}. Expected ${serializedP256Type} (P256) or ${serializedWebAuthnType} (WebAuthn)`, serialized, }); } function from(value) { if (typeof value === 'string') return deserialize(value); if (typeof value === 'object' && value !== null && 'r' in value && 's' in value && 'yParity' in value) return { signature: value, type: 'secp256k1' }; const type = getType(value); return { ...value, ...(type === 'p256' ? { prehash: value.prehash } : {}), type, }; } function fromRpc(envelope) { if (envelope.type === 'secp256k1') return { signature: Signature.fromRpc(envelope), type: 'secp256k1', }; if (envelope.type === 'p256') { return { prehash: envelope.preHash, publicKey: { prefix: 4, x: Hex.toBigInt(envelope.pubKeyX), y: Hex.toBigInt(envelope.pubKeyY), }, signature: { r: Hex.toBigInt(envelope.r), s: Hex.toBigInt(envelope.s), }, type: 'p256', }; } if (envelope.type === 'webAuthn') { const webauthnData = envelope.webauthnData; const webauthnDataSize = Hex.size(webauthnData); let authenticatorData; let clientDataJSON; for (let split = 37; split < webauthnDataSize; split++) { const potentialJson = Hex.toString(Hex.slice(webauthnData, split)); if (potentialJson.startsWith('{') && potentialJson.endsWith('}')) { try { JSON.parse(potentialJson); authenticatorData = Hex.slice(webauthnData, 0, split); clientDataJSON = potentialJson; break; } catch { } } } if (!authenticatorData || !clientDataJSON) throw new InvalidSerializedError({ reason: 'Unable to parse WebAuthn metadata: could not extract valid authenticatorData and clientDataJSON', serialized: webauthnData, }); return { metadata: { authenticatorData, clientDataJSON, }, publicKey: { prefix: 4, x: Hex.toBigInt(envelope.pubKeyX), y: Hex.toBigInt(envelope.pubKeyY), }, signature: { r: Hex.toBigInt(envelope.r), s: Hex.toBigInt(envelope.s), }, type: 'webAuthn', }; } if (envelope.type === 'keychain' || ('userAddress' in envelope && 'signature' in envelope)) return { type: 'keychain', userAddress: envelope.userAddress, inner: fromRpc(envelope.signature), }; throw new CoercionError({ envelope }); } function getType(envelope) { if (typeof envelope !== 'object' || envelope === null) throw new CoercionError({ envelope }); if ('type' in envelope && envelope.type) return envelope.type; if ('signature' in envelope && !('publicKey' in envelope) && typeof envelope.signature === 'object' && envelope.signature !== null && 'r' in envelope.signature && 's' in envelope.signature && 'yParity' in envelope.signature) return 'secp256k1'; if ('r' in envelope && 's' in envelope && 'yParity' in envelope) return 'secp256k1'; if ('signature' in envelope && 'prehash' in envelope && 'publicKey' in envelope && typeof envelope.prehash === 'boolean') return 'p256'; if ('signature' in envelope && 'metadata' in envelope && 'publicKey' in envelope) return 'webAuthn'; if ('userAddress' in envelope && 'inner' in envelope) return 'keychain'; throw new CoercionError({ envelope, }); } function serialize(envelope, options = {}) { const type = getType(envelope); if (type === 'secp256k1') { const secp256k1 = envelope; return Hex.concat(Signature.toHex(secp256k1.signature), options.magic ? exports.magicBytes : '0x'); } if (type === 'p256') { const p256 = envelope; return Hex.concat(serializedP256Type, Hex.fromNumber(p256.signature.r, { size: 32 }), Hex.fromNumber(p256.signature.s, { size: 32 }), Hex.fromNumber(p256.publicKey.x, { size: 32 }), Hex.fromNumber(p256.publicKey.y, { size: 32 }), Hex.fromNumber(p256.prehash ? 1 : 0, { size: 1 }), options.magic ? exports.magicBytes : '0x'); } if (type === 'webAuthn') { const webauthn = envelope; const webauthnData = Hex.concat(webauthn.metadata.authenticatorData, Hex.fromString(webauthn.metadata.clientDataJSON)); return Hex.concat(serializedWebAuthnType, webauthnData, Hex.fromNumber(webauthn.signature.r, { size: 32 }), Hex.fromNumber(webauthn.signature.s, { size: 32 }), Hex.fromNumber(webauthn.publicKey.x, { size: 32 }), Hex.fromNumber(webauthn.publicKey.y, { size: 32 }), options.magic ? exports.magicBytes : '0x'); } if (type === 'keychain') { const keychain = envelope; return Hex.concat(serializedKeychainType, keychain.userAddress, serialize(keychain.inner), options.magic ? exports.magicBytes : '0x'); } throw new CoercionError({ envelope }); } function toRpc(envelope) { const type = getType(envelope); if (type === 'secp256k1') { const secp256k1 = envelope; return { ...Signature.toRpc(secp256k1.signature), type: 'secp256k1', }; } if (type === 'p256') { const p256 = envelope; return { preHash: p256.prehash, pubKeyX: Hex.fromNumber(p256.publicKey.x, { size: 32 }), pubKeyY: Hex.fromNumber(p256.publicKey.y, { size: 32 }), r: Hex.fromNumber(p256.signature.r, { size: 32 }), s: Hex.fromNumber(p256.signature.s, { size: 32 }), type: 'p256', }; } if (type === 'webAuthn') { const webauthn = envelope; const webauthnData = Hex.concat(webauthn.metadata.authenticatorData, Hex.fromString(webauthn.metadata.clientDataJSON)); return { pubKeyX: Hex.fromNumber(webauthn.publicKey.x, { size: 32 }), pubKeyY: Hex.fromNumber(webauthn.publicKey.y, { size: 32 }), r: Hex.fromNumber(webauthn.signature.r, { size: 32 }), s: Hex.fromNumber(webauthn.signature.s, { size: 32 }), type: 'webAuthn', webauthnData, }; } if (type === 'keychain') { const keychain = envelope; return { type: 'keychain', userAddress: keychain.userAddress, signature: toRpc(keychain.inner), }; } throw new CoercionError({ envelope }); } function validate(envelope) { try { assert(envelope); return true; } catch { return false; } } function verify(signature, parameters) { const { payload } = parameters; const address = (() => { if (parameters.address) return parameters.address; if (parameters.publicKey) return Address.fromPublicKey(parameters.publicKey); return undefined; })(); if (!address) return false; const envelope = from(signature); if (envelope.type === 'secp256k1') { if (!address) return false; return ox_Secp256k1.verify({ address, payload, signature: envelope.signature, }); } if (envelope.type === 'p256') { const envelopeAddress = Address.fromPublicKey(envelope.publicKey); if (!Address.isEqual(envelopeAddress, address)) return false; return ox_P256.verify({ hash: envelope.prehash, publicKey: envelope.publicKey, payload, signature: envelope.signature, }); } if (envelope.type === 'webAuthn') { const envelopeAddress = Address.fromPublicKey(envelope.publicKey); if (!Address.isEqual(envelopeAddress, address)) return false; return ox_WebAuthnP256.verify({ challenge: Hex.from(payload), metadata: envelope.metadata, publicKey: envelope.publicKey, signature: envelope.signature, }); } throw new VerificationError(`Unable to verify signature envelope of type "${envelope.type}".`); } class CoercionError extends Errors.BaseError { constructor({ envelope }) { super(`Unable to coerce value (\`${Json.stringify(envelope)}\`) to a valid signature envelope.`); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'SignatureEnvelope.CoercionError' }); } } exports.CoercionError = CoercionError; class MissingPropertiesError extends Errors.BaseError { constructor({ envelope, missing, type, }) { super(`Signature envelope of type "${type}" is missing required properties: ${missing.map((m) => `\`${m}\``).join(', ')}.\n\nProvided: ${Json.stringify(envelope)}`); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'SignatureEnvelope.MissingPropertiesError' }); } } exports.MissingPropertiesError = MissingPropertiesError; class InvalidSerializedError extends Errors.BaseError { constructor({ reason, serialized, }) { super(`Unable to deserialize signature envelope: ${reason}`, { metaMessages: [`Serialized: ${serialized}`], }); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'SignatureEnvelope.InvalidSerializedError' }); } } exports.InvalidSerializedError = InvalidSerializedError; class VerificationError extends Errors.BaseError { constructor() { super(...arguments); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'SignatureEnvelope.VerificationError' }); } } exports.VerificationError = VerificationError; //# sourceMappingURL=SignatureEnvelope.js.map