
Security News
Risky Biz Podcast: Making Reachability Analysis Work in Real-World Codebases
This episode explores the hard problem of reachability analysis, from static analysis limits to handling dynamic languages and massive dependency trees.
@ngraveio/ur-blockchain-commons
Advanced tools
A JS implementation of Uniform Resources(UR) Registry specification from Blockchain Commons.
This repository is an implementation of latests the BC-UR Registry specification
It is refactored version of Keystone bc-ur-registry with BC-UR Version 2
Supported UR types:
UR Type | CBOR Tag | Description | Definition |
---|---|---|---|
hdkey crypto-hdkey | 40303 | Hierarchical Deterministic (HD) key | [BCR-2020-007] |
keypath crypto-keypath | 40304 | Key Derivation Path | [BCR-2020-007] |
coin-info crypto-coin-info | 40305 | Cryptocurrency Coin Use | [BCR-2020-007] |
eckey crypto-eckey | 40306 | Elliptic Curve (EC) key | [BCR-2020-008] |
address crypto-address | 40307 | Cryptocurrency Address | [BCR-2020-009] |
output-descriptor crypto-output | 40308 | Bitcoin Output Descriptor | [BCR-2023-010] |
psbt crypto-psbt | 40310 | Partially Signed Bitcoin Transaction (PSBT) | [BCR-2020-0006] |
account-descriptor crypto-account | 40311 | BIP44 Account | [BCR-2023-019] |
To install, run:
yarn add @ngraveio/ur-blockchain-commons
npm install --save @ngraveio/ur-blockchain-commons
Exported types:
import {
Bytes,
CoinInfo, Network,
PSBT,
Keypath, PathComponent,
HDKey,
ECKey,
Address,
AddressScriptType,
OutputDescriptor,
AccountDescriptor,
} from '@ngraveio/ur-blockchain-commons'
UR Type | CBOR Tag |
---|---|
psbt | NaN |
The type psbt contains a single, deterministic length byte string of variable length up to 2^32-1 bytes. Semantically, this byte string MUST be a valid Partially Signed Bitcoin Transaction encoded in the binary format specified by [BIP174].
It encodes CBOR bytes type without any tag.
Usage:
import { PSBT } from "@ngraveio/ur-blockchain-commons";
const psbtBytes = Buffer.from("70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000", "hex");
const psbt = new PSBT(psbtBytes);
psbt.toUr();
// ur:psbt/hdosjojkidjyzmadaenyaoaeaeaeaohdvsknclrejnpebncnrnmnjojofejzeojlkerdonspkpkkdkykfelokgprpyutkpaeaeaeaeaezmzmzmzmlslgaaditiwpihbkispkfgrkbdaslewdfycprtjsprsgksecdratkkhktikewdcaadaeaeaeaezmzmzmzmaojopkwtayaeaeaeaecmaebbtphhdnjstiambdassoloimwmlyhygdnlcatnbggtaevyykahaeaeaeaecmaebbaeplptoevwwtyakoonlourgofgvsjydpcaltaemyaeaeaeaeaeaeaeaeaebkgdcarh
UR Type | CBOR Tag |
---|---|
coin-info | 40305 |
Definition: https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-007-hdkey.md
Has 2 parameters type
BIP44 coin type and network
that highlights testnet or mainnet for Bitcoin like coins and for ethereum based coins it is chainId
Usage:
import { CoinInfo, Network } from "@ngraveio/ur-blockchain-commons";
const bitcoinMainnet = new CoinInfo(); // Default is Bitcoin Mainnet
const bitcoinTestnet = new CoinInfo(undefined, Network.Testnet);
const ethereumMainnet = new CoinInfo(60); // Ethereum Mainnet
const ethereumTestnet = new CoinInfo(60, Network.Testnet); // Ethereum Testnet
bitcoinMainnet.getType(); // 0
bitcoinMainnet.getNetwork(); // 0 for mainnet
UR Type | CBOR Tag |
---|---|
keypath | 40304 |
Definition: https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-007-hdkey.md
Keypath class for handling BIP44 like hierarchical key derivation paths.
Example path in string format:
44'/0'/0'/0/0
1'/2/3-4/5-6'/*/*'/<7;8'>/<9';0>
Input arguments:
path
: String path or array of PathComponents
that make up the path.source-fingerprint
: The fingerprint of the ancestor or master key. Required if components
is empty.depth
: The number of derivation steps in the path. If omitted, it will be inferred from components
.Usage:
import { KeyPath, PathComponent } from "@ngraveio/ur-blockchain-commons";
// Create a KeyPath from a string
// This path containes Simple index, Range, Wildcard, Pair
const path = "1'/2/3-4/5-6'/*/*'/<7;8'>/<9';0>";
const keyPath = new KeyPath({sourceFingerprint: 123456789, path: path});
keyPath.setDepth(); // Automatically set the depth of the keypath
// This will save path as array of PathComponents
const components = keyPath.getComponents();
keyPath.getSourceFingerprint(); // 123456789
keyPath.getDepth(); // 8
keyPath.toString(); // 1'/2/3-4/5-6'/*/*'/<7;8'>/<9';0>
keyPath.toString('h'); // 1h/2/3-4/5-6h/*/*'/<7h;0>
// Create from components
const comp1 = new PathComponent({ index: 98, hardened: true });
const comp2 = new PathComponent({ range: [2, 6] });
const comp3 = new PathComponent({ wildcard: true, hardened: true });
const comp4 = new PathComponent({ pair: [{ index: 78200, hardened: true }, { index: 0, hardened: true } ] });
// Encoding
const keyPath2 = new Keypath({ path: [comp1, comp2, comp3, comp4] });
keyPath2.toString(); // 98'/2-6/*/*'/<78200h;0h>
KeyPath
consists of following PathComponents
:
'
or h
for hardening.
0'
or 0h
are valid hardened indices.5
is a non-hardened index.const index = new PathComponent({index: 0, hardened: true}); // 0h
const index = new PathComponent({index: 0, hardened: false}); // 0
start-end
where start and end are integers.
1-5
is a valid range.1'-5
is invalid because hardening is not applied to the second element in the range.const range = new PathComponent({start: 1, end: 5}); // 1-5
const range = new PathComponent({start: 1, end: 5, hardened: true}); // 1-5'
*
is used to derive all possible children at that level.
*h
is also valid for hardened wildcard.const wildcard = new PathComponent({wildcard: true}); // *
const wildcard = new PathComponent({wildcard: true, hardened: true}); // *h
<external;internal>
.
<0;1>
is a valid pair.<0h;1>
is also valid for hardened external index.<0;1h>
is also valid for hardened internal index.<0h;1h>
is also valid for hardened external and internal indices.const pair = new PathComponent({ pair: [{ index: 1, hardened: false }, { index: 0, hardened: true } ] }); // <1;0h>
Valid Path String Rules:
44'/0'/0'/0/0
).1-6'
; 1h-6
is invalid).*
) at any depth (e.g., 44'/0'/0'/*
).<0h;1h>
or <0;1>
).44'/0'/1'-5'/<0h;1h>/*
).0x80000000
.'
or h
) must immediately follow the index.m
, but it is not required./
and contain integers, ranges, wildcards, or pairs.UR Type | CBOR Tag |
---|---|
hdkey | 40303 |
Definition: https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-007-hdkey.md#cddl-for-hdkey
Hierarchical Deterministic (HD) Keys follow the BIP32 standard, enabling secure and structured management of cryptocurrency keys. HDKeys can be serialized in the xpub
format and are compatible with wallets and platforms using this standard.
The HDKey
class can be instantiated with the following arguments:
interface HDKeyConstructorArgs {
isMaster?: boolean
keyData: Buffer
chainCode?: Buffer
isPrivateKey?: boolean
useInfo?: CoinInfo
origin?: Keypath
children?: Keypath
parentFingerprint?: number
name?: string
note?: string
}
isMaster
(optional):
Indicates whether this key is the master key. Defaults to false
.
keyData
(required):
A Buffer
containing the key's raw data. For public keys, this must be 33 bytes. For private keys, it should be 34 bytes (0x00
prepended to 32-byte private key data).
chainCode
(optional):
A Buffer
containing the chain code (32 bytes). Required for deriving additional keys.
isPrivateKey
(optional):
Indicates whether this key is a private key. Defaults to false
.
useInfo
(optional):
A CoinInfo
object specifying how this key is intended to be used (e.g., mainnet/testnet or coin type like Bitcoin or Ethereum).
origin
(optional):
A Keypath
object describing the derivation path that led to this key (e.g., m/44'/0'/0'
).
children
(optional):
A Keypath
object defining the derivation rules for child keys (e.g., 0/*
for all external child keys).
parentFingerprint
(optional):
A 4-byte number representing the fingerprint of the parent key. Defaults to 0
for master keys.
name
(optional):
A string specifying a short, human-readable name for the key.
note
(optional):
A string for storing additional text or notes describing the key.
A master-key
is the root of an HDKey hierarchy, used as the starting point for generating derived keys.
import { HDKey } from '@ngraveio/ur-blockchain-commons';
// Example: Creating a master key
const masterKey = new HDKey({
isMaster: true,
keyData: Buffer.from('your-private-key-data', 'hex'), // 33 bytes
chainCode: Buffer.from('your-chain-code', 'hex'), // 32 bytes
});
// Access properties
console.log(masterKey.isMaster); // true
console.log(masterKey.getKeyData().toString('hex')); // your-private-key-data
console.log(masterKey.getChainCode().toString('hex')); // your-chain-code
You can create an HDKey from an extended public key (xpub). This is useful for securely deriving public keys without exposing the private key.
// Example: Creating an HDKey from a Bitcoin mainnet xpub
const bitcoinXpub = 'xpub661MyMwAqRbcFtXgS5s...'; // Replace with actual xpub
const hdKey = HDKey.fromXpub(bitcoinXpub);
console.log(hdKey.isPrivate); // false (public key)
console.log(hdKey.getKeyData().toString('hex')); // Public key data
console.log(hdKey.getChainCode().toString('hex')); // Chain code
origin
: Specifies the derivation path leading to the current key (e.g., m/44'/0'/0'
for Bitcoin mainnet).children
: Specifies the derivation rules for child keys (e.g., 0/*
to derive all external addresses).import { KeyPath } from '@ngraveio/ur-blockchain-commons';
// Example: Derived key with metadata
const keyWithMetadata = new HDKey({
keyData: Buffer.from('child-public-key', 'hex'),
chainCode: Buffer.from('child-chain-code', 'hex'),
origin: new KeyPath({ path: "m/44'/0'/0'" }), // Bitcoin mainnet derivation
children: new KeyPath({ path: "0/*" }), // Derive all external addresses
});
console.log(keyWithMetadata.getOrigin().toString()); // m/44'/0'/0'
console.log(keyWithMetadata.getChildren().toString()); // 0/*
Ethereum follows the BIP44
standard, where the coin type is 60
. Here’s an example of using an Ethereum xpub to create an HDKey.
// Example: Creating an HDKey from an Ethereum xpub
const ethereumXpub = 'xpub6Dkxxxx'; // Replace with actual Ethereum xpub
const ethereumHDKey = HDKey.fromXpub(ethereumXpub);
console.log(ethereumHDKey.getKeyData().toString('hex')); // Ethereum public key
console.log(ethereumHDKey.isPrivate); // false
HDKey.fromXpub(xpub: string)
: Create an HDKey from an extended public key.getKeyData()
: Retrieve the key data (public or private).getChainCode()
: Get the chain code for further derivations.getOrigin()
: Access the derivation origin.getChildren()
: Access child key derivation rules.getParentFingerprint()
: Retrieve the parent fingerprint for the key.HDKeys encoded according to BIP32 are represented as a BASE58-CHECK encoded string, such as:
xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8
0x0488B21E
(mainnet public key)0x0488ADE4
(mainnet private key)0x043587CF
(testnet public key)0x04358394
(testnet private key)0x00
for master nodes.0x01
for level-1 derived keys, and so on.0x00000000
for master keys.0x00000000
for master keys.ser32(i)
for index i
in derived keys.serP(K)
.0x00 || ser256(k)
.This binary serialization is then BASE58-CHECK encoded, adding a 4-byte checksum at the end.
To maintain compatibility with the BIP32 standard, an HDKey
must include the following fields for proper derivation:
chain-code
: Used for deriving further keys.origin
: Describes the derivation path to the key (e.g., m/44'/0'/0'
).parent-fingerprint
: The fingerprint of the key's direct ancestor.Important:
origin
contains only a single derivation step and includes a source-fingerprint
, the parent-fingerprint
must be identical to the source-fingerprint
or can be omitted.UR Type | CBOR Tag |
---|---|
address | 40307 |
Definition: https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-009-address.md
The Address
class represents a cryptocurrency address, such as a Bitcoin or Ethereum address. It encapsulates the address data and metadata, including the coin type and network (mainnet or testnet).
interface IAddressInput {
/** Type of the coin and network (testnet, mainnet) */
info?: CoinInfo // When omitted defaults to bitcoin mainnet
/**
* The `type` field MAY be included for Bitcoin (and similar cryptocurrency) addresses, and MUST be omitted for non-applicable types.
* For bitcoin script type eg: p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, P2TR
**/
type?: AddressScriptType
/** Public key or script hash that is encoded */
data: Uint8Array | Buffer
}
import { Address, CoinInfo, Network } from '@ngraveio/ur-blockchain-commons';
// Example: Creating an Ethereum testnet address
const ethereumTestnet = new Address({
data: Buffer.from("81b7e08f65bdf5648606c89998a9cc8164397647", "hex");
info: new CoinInfo(60, 1), // BIP44 coin type 60 (ethereum), testnet
});
ethereumTestnet.toString(); // 0x81b7e08f65bdf5648606c89998a9cc8164397647
// Creating a Bitcoin mainnet address P2PKH
const bitcoinMainnet = new Address({
data: Buffer.from("77bff20c60e522dfaa3350c39b030a5d004e839a", "hex"),
// default info is bitcoin P2PKH
});
bitcoinMainnet.toString(); // 1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2
fromAddress
method can be used to create an Address
object from a string address.
It currently supports Bitcoin and Ethereum addresses.
It can infer mainnet or testnet and script type for Bitcoin addresses. But it cannot infer the chainId for Ethereum addresses. So it needs to be given explicitly.
// Example: Creating an Ethereum testnet address
const ethereumTestnet = Address.fromAddress("0x81b7e08f65bdf5648606c89998a9cc8164397647", 'testnet');
ethereumTestnet.toString(); // 0x81b7e08f65bdf5648606c89998a9cc8164397647
// Creating a Bitcoin testnet address P2PKH
const bitcoinP2WPKHtestnet = Address.fromAddress("tb1q0mt7t7sjn777f4mgpk7u67a82aykkw3kq4kaad");
bitcoinP2WPKHtestnet.toString(); // tb1q0mt7t7sjn777f4mgpk7u67a82aykkw3kq4kaad
bitcoinP2WPKHtestnet.getAddressInfo().getType() // BIP44 `0` bitcoin
bitcoinP2WPKHtestnet.getAddressInfo().getNetwork() // 1 for testnet
bitcoinP2WPKHtestnet.getAddressScriptType() // AddressScriptType.P2WPKH
Address Type | Starts With | Version Byte (Mainnet) | Version Byte (Testnet) | Encoding Type | Prefix Application | Mainnet Example | Testnet Example |
---|---|---|---|---|---|---|---|
Pay-to-Public-Key (P2PK) | No address format | N/A | N/A | Script-based | No address prefix; directly uses public key in scripts. | No specific address; script usage only. | No specific address; script usage only. |
Pay-to-Public-Key-Hash (P2PKH) | 1 | 0x00 | 0x6F | Base58Check | Add the version byte (0x00 or 0x6F) before the hashed public key and checksum. | 18uWvCS2hqV6D5ehQtDJxrftrePAXGeevS | ms5e572mZ1eDKdeyfR6MpRqXHVv6kM6wAP |
Pay-to-Script-Hash (P2SH) | 3 | 0x05 | 0xC4 | Base58Check | Add the version byte (0x05 or 0xC4) before the script hash and checksum. | 3FymWfwDaGzsRWesK47nxFWPDiDmkC8GkR | 2MvJq3ieuKUiwvQP1WVQdfb5WB5fMStTkhH |
Pay-to-Witness-Public-Key-Hash (P2WPKH) | bc1q | Witness Version 0 (0x00) | Witness Version 0 (0x00) | Bech32 | Add the human-readable prefix (bc or tb) and encode the data with Bech32. | bc1q26mhhmkkddq9zd66fec6tac2lp07c7uuaurgtr | tb1q0mt7t7sjn777f4mgpk7u67a82aykkw3kq4kaad |
Pay-to-Witness-Script-Hash (P2WSH) | bc1q | Witness Version 0 (0x00) | Witness Version 0 (0x00) | Bech32 | Add the human-readable prefix (bc or tb) and encode the data with Bech32. | bc1q6axwlnwlky7jykqqwlrcjy2s6ragcwaesal0nfpv5pnwdmgu72es5kywz8f | tb1qwjnw4rf07n8wyerlnplyeecpfkw5q2puqn0vux04kqpdu689qx0qx6uqvj |
Pay-to-Taproot (P2TR) | bc1p | Witness Version 1 (0x01) | Witness Version 1 (0x01) | Bech32m | Add the human-readable prefix (bc or tb) and encode the data with Bech32m. | bc1p9cjtuu7rlytzgeuwtdy4fuflmpp00tmpwchr7xjdexs5la94frkqpmcs8f | tb1p34jjsay897lryzkc0fkxk9wruhvct6vnmknxxaxy75rxnpakqlqs56v2lh |
Pay-to-Multisig (P2MS) | No address format | N/A | N/A | Script-based | No address prefix; directly uses multisignature script. | No specific address; script usage only. | No specific address; script usage only. |
UR Type | CBOR Tag |
---|---|
output-descriptor | 40308 |
Definition: https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2023-010-output-descriptor.md
Output descriptors [OD-IN-CORE], [OSD], also called output script descriptors, are a way of specifying Bitcoin payment outputs that can range from a simple address to multisig and segwit using a simple domain-specific language. For more on the motivation for output descriptors, see [WHY-OD].
Important Note:
Output descritor string parsing is not implemented in this library. So you need to manually parse the output descriptor string and create
HDKey
,ECKey
, andAddress
objects and pass them to theOutputDescriptor
class.
Input Arguments:
text
: The output descriptor string.keys
(optional): An array of keys that are defined as HDKey
, Eckey
, or Address
classes.name
(optional): A human-readable name for the descriptor.note
(optional): Additional notes or comments.Usage:
import { OutputDescriptor, HDKey, ECKey } from '@ngraveio/ur-blockchain-commons';
// pk(03e220e776d811c44075a4a260734445c8967865f5357ba98ead3bc6a6552c36f2)
const text = "pk(@0)"
const eckey = new ECKey({
data: Buffer.from("03e220e776d811c44075a4a260734445c8967865f5357ba98ead3bc6a6552c36f2", "hex")
});
const outputDescriptor = new OutputDescriptor({
source: text,
keys: [eckey],
});
More advanced example with HDKeys:
import { OutputDescriptor, HDKey, ECKey } from '@ngraveio/ur-blockchain-commons';
const text = 'wsh(sortedmulti(2,@0,@1,@2))'
// Create HDKey objects
const hdkey1 = HDKey.fromXpub("xpub6DiYrfRwNnjeX4vHsWMajJVFKrbEEnu8gAW9vDuQzgTWEsEHE16sGWeXXUV1LBWQE1yCTmeprSNcqZ3W74hqVdgDbtYHUv3eM4W2TEUhpan");
const sourceFingerprint = Buffer.from("dc567276", "hex").readUint32BE();
hdkey1.data.origin = new Keypath({
sourceFingerprint: sourceFingerprint,
path: "48'/0'/0'/2'"
});
// @ts-ignore
hdkey1.data.children = new Keypath({
path: "<0;1>/*",
});
const hdkey2 = HDKey.fromXpub("xpub6DnT4E1fT8VxuAZW29avMjr5i99aYTHBp9d7fiLnpL5t4JEprQqPMbTw7k7rh5tZZ2F5g8PJpssqrZoebzBChaiJrmEvWwUTEMAbHsY39Ge");
hdkey2.data.origin = new Keypath({
sourceFingerprint: Buffer.from("f245ae38", "hex").readUint32BE(),
path: "48'/0'/0'/2'"
});
// @ts-ignore
hdkey2.data.children = new Keypath({
path: "<0;1>/*",
});
const hdkey3 = HDKey.fromXpub("xpub6DjrnfAyuonMaboEb3ZQZzhQ2ZEgaKV2r64BFmqymZqJqviLTe1JzMr2X2RfQF892RH7MyYUbcy77R7pPu1P71xoj8cDUMNhAMGYzKR4noZ");
hdkey3.data.origin = new Keypath({
sourceFingerprint: Buffer.from("c5d87297", "hex").readUint32BE(),
path: "48'/0'/0'/2'"
});
hdkey3.data.children = new Keypath({
path: "<0;1>/*",
});
const outputDescriptor = new OutputDescriptor({
source: text,
keys: [hdkey1, hdkey2, hdkey3],
name: "Satoshi's Stash",
});
UR Type | CBOR Tag |
---|---|
account-descriptor | 40311 |
Definition: https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2023-019-account-descriptor.md
The AccountDescriptor
promotes standards-based sharing of BIP44 account level xpubs and other information, allowing devices to join wallets with minimal user interaction. It addresses the need for devices to share xpubs at the correct derivation path for various script types, reducing the burden on users to select the script type manually. This standard format bundles information for multiple script types into a set, enabling wallet software to select the appropriate type from the set provided by the device.
interface IAccountDescriptorArgs {
masterFingerprint: number
outputDescriptors: OutputDescriptor[]
}
import { AccountDescriptor, OutputDescriptor } from '@ngraveio/ur-blockchain-commons';
// Example: Creating an AccountDescriptor
const outputDescriptor = new OutputDescriptor({
// ...output descriptor initialization...
});
const accountDescriptor = new AccountDescriptor({
masterFingerprint: 1234567890,
outputDescriptors: [outputDescriptor],
});
// Access properties
console.log(accountDescriptor.getMasterFingerprint()); // 1234567890
console.log(accountDescriptor.getOutputDescriptors()); // [outputDescriptor]
The AccountDescriptor
class provides methods to access the master fingerprint and output descriptors:
getMasterFingerprint()
: Returns the master fingerprint.getOutputDescriptors()
: Returns the array of output descriptors.const masterFingerprint = accountDescriptor.getMasterFingerprint();
const outputDescriptors = accountDescriptor.getOutputDescriptors();
This class ensures that output descriptors are restricted to HD keys at account level key derivations only, as per the BIP44 standard.
FAQs
A JS implementation of Uniform Resources(UR) Registry specification from Blockchain Commons.
The npm package @ngraveio/ur-blockchain-commons receives a total of 6 weekly downloads. As such, @ngraveio/ur-blockchain-commons popularity was classified as not popular.
We found that @ngraveio/ur-blockchain-commons demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 5 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
This episode explores the hard problem of reachability analysis, from static analysis limits to handling dynamic languages and massive dependency trees.
Security News
/Research
Malicious Nx npm versions stole secrets and wallet info using AI CLI tools; Socket’s AI scanner detected the supply chain attack and flagged the malware.
Security News
CISA’s 2025 draft SBOM guidance adds new fields like hashes, licenses, and tool metadata to make software inventories more actionable.