@radixdlt/rola
Version:
Radix TypeScript ROLA library
215 lines (204 loc) • 7.65 kB
JavaScript
// 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
};