UNPKG

@radixdlt/rola

Version:

Radix TypeScript ROLA library

215 lines (204 loc) 7.65 kB
// src/rola.ts import { ResultAsync as ResultAsync3, err as err3, errAsync as errAsync2, ok as ok3 } from "neverthrow"; // src/helpers/create-signature-message.ts import { Buffer as Buffer2 } from "buffer"; // src/crypto/blake2b.ts import { err, ok } from "neverthrow"; import blake from "blakejs"; import { Buffer } from "buffer"; var toArrayBuffer = (buffer) => { const arrayBuffer = new ArrayBuffer(buffer.length); const view = new Uint8Array(arrayBuffer); for (let i = 0; i < buffer.length; ++i) { view[i] = buffer[i]; } return arrayBuffer; }; var bufferToUnit8Array = (buffer) => new Uint8Array(toArrayBuffer(buffer)); var blake2b = (input) => { try { return ok(blake.blake2bHex(bufferToUnit8Array(input), void 0, 32)).map( (hex) => Buffer.from(hex, "hex") ); } catch (error) { return err(error); } }; // src/helpers/create-signature-message.ts var createSignatureMessage = ({ challenge, dAppDefinitionAddress, origin }) => { const prefix = Buffer2.from("R", "ascii"); const lengthOfDappDefAddress = dAppDefinitionAddress.length; const lengthOfDappDefAddressBuffer = Buffer2.from( lengthOfDappDefAddress.toString(16), "hex" ); const dappDefAddressBuffer = Buffer2.from(dAppDefinitionAddress, "utf-8"); const originBuffer = Buffer2.from(origin, "utf-8"); const challengeBuffer = Buffer2.from(challenge, "hex"); const messageBuffer = Buffer2.concat([ prefix, challengeBuffer, lengthOfDappDefAddressBuffer, dappDefAddressBuffer, originBuffer ]); return blake2b(messageBuffer).map((hash) => Buffer2.from(hash).toString("hex")).mapErr((jsError) => ({ reason: "couldNotHashMessage", jsError })); }; // src/helpers/verify-proof.ts import { err as err2, ok as ok2 } from "neverthrow"; import { secp256k1 } from "@noble/curves/secp256k1"; import { ed25519 } from "@noble/curves/ed25519"; var supportedCurves = /* @__PURE__ */ new Set(["curve25519", "secp256k1"]); var verifyProofFactory = (input) => (signatureMessageHex) => { const isSupportedCurve = supportedCurves.has(input.proof.curve); if (!isSupportedCurve) return err2({ reason: "unsupportedCurve" }); try { let isValid = false; if (input.proof.curve === "curve25519") { isValid = ed25519.verify( input.proof.signature, signatureMessageHex, input.proof.publicKey ); } else { const signature = input.proof.signature.slice(2); isValid = secp256k1.verify( signature, signatureMessageHex, input.proof.publicKey ); } return isValid ? ok2(void 0) : err2({ reason: "invalidSignature" }); } catch (error) { return err2({ reason: "invalidPublicKey", jsError: error }); } }; // src/helpers/derive-address-from-public-key.ts import { PublicKey, RadixEngineToolkit } from "@radixdlt/radix-engine-toolkit"; import { ResultAsync, errAsync } from "neverthrow"; // src/helpers/typed-error.ts var typedError = (error) => error; // src/helpers/derive-address-from-public-key.ts var deriveVirtualIdentityAddress = (publicKey, networkId) => ResultAsync.fromPromise( RadixEngineToolkit.Derive.virtualIdentityAddressFromPublicKey( new PublicKey.Ed25519(publicKey), networkId ), typedError ); var deriveVirtualEddsaEd25519AccountAddress = (publicKey, networkId) => ResultAsync.fromPromise( RadixEngineToolkit.Derive.virtualAccountAddressFromPublicKey( new PublicKey.Ed25519(publicKey), networkId ), typedError ); var deriveVirtualEcdsaSecp256k1AccountAddress = (publicKey, networkId) => ResultAsync.fromPromise( RadixEngineToolkit.Derive.virtualAccountAddressFromPublicKey( new PublicKey.Secp256k1(publicKey), networkId ), typedError ); var deriveVirtualAddress = (signedChallenge, networkId) => { if (signedChallenge.type === "persona") return deriveVirtualIdentityAddress( signedChallenge.proof.publicKey, networkId ); else if (signedChallenge.type === "account" && signedChallenge.proof.curve === "curve25519") return deriveVirtualEddsaEd25519AccountAddress( signedChallenge.proof.publicKey, networkId ); else if (signedChallenge.type === "account" && signedChallenge.proof.curve === "secp256k1") return deriveVirtualEcdsaSecp256k1AccountAddress( signedChallenge.proof.publicKey, networkId ); return errAsync(new Error("Could not derive virtual address")); }; // src/helpers/create-public-key-hash.ts import { Buffer as Buffer3 } from "buffer"; var createPublicKeyHash = (publicKey) => blake2b(Buffer3.from(publicKey, "hex")).map((hash) => hash.subarray(-29)).map((hash) => Buffer3.from(hash).toString("hex")); // src/gateway.ts import { GatewayApiClient, RadixNetworkConfigById } from "@radixdlt/babylon-gateway-api-sdk"; import { ResultAsync as ResultAsync2 } from "neverthrow"; var GatewayService = (input) => { var _a; const { networkId, applicationName } = input; const config = RadixNetworkConfigById[networkId]; if (!config) throw new Error(`Network ${networkId} not found`); const { state } = (_a = input.gatewayApiClient) != null ? _a : GatewayApiClient.initialize({ basePath: config.gatewayUrl, applicationName }); const getEntityDetails = (address) => ResultAsync2.fromPromise( state.getEntityDetailsVaultAggregated(address), typedError ); return { getEntityOwnerKeys: (address) => getEntityDetails(address).map( (response) => { var _a2, _b, _c; return (_c = (_b = (_a2 = response == null ? void 0 : response.metadata) == null ? void 0 : _a2.items.find((item) => item.key === "owner_keys")) == null ? void 0 : _b.value.raw_hex) != null ? _c : ""; } ) }; }; // src/rola.ts if (!globalThis.self) globalThis.self = globalThis; var Rola = (input) => { const { expectedOrigin, dAppDefinitionAddress, networkId, applicationName } = input; const gatewayService = GatewayService({ networkId, applicationName, gatewayApiClient: input.gatewayApiClient }); const verifySignedChallenge = (signedChallenge) => { const result = createPublicKeyHash(signedChallenge.proof.publicKey); if (result.isErr()) return errAsync2({ reason: "couldNotHashPublicKey" }); const hashedPublicKey = result.value; const verifyProof = verifyProofFactory(signedChallenge); const getDerivedAddress = () => deriveVirtualAddress(signedChallenge, networkId).mapErr((jsError) => ({ reason: "couldNotDeriveAddressFromPublicKey", jsError })); const queryLedger = () => gatewayService.getEntityOwnerKeys(signedChallenge.address).mapErr((jsError) => ({ reason: "couldNotVerifyPublicKeyOnLedger", jsError })).map((ownerKeys) => ({ ownerKeysMatchesProvidedPublicKey: ownerKeys.toUpperCase().includes(hashedPublicKey.toUpperCase()), ownerKeysSet: !!ownerKeys })); const deriveAddressFromPublicKeyAndQueryLedger = () => ResultAsync3.combine([getDerivedAddress(), queryLedger()]); return createSignatureMessage({ dAppDefinitionAddress, origin: expectedOrigin, challenge: signedChallenge.challenge }).andThen(verifyProof).asyncAndThen(deriveAddressFromPublicKeyAndQueryLedger).andThen( ([ derivedAddress, { ownerKeysMatchesProvidedPublicKey, ownerKeysSet } ]) => { const derivedAddressMatchesPublicKey = !ownerKeysSet && derivedAddress === signedChallenge.address; return ownerKeysMatchesProvidedPublicKey || derivedAddressMatchesPublicKey ? ok3(void 0) : err3({ reason: "invalidPublicKey" }); } ); }; return { verifySignedChallenge }; }; export { Rola };