Comparing version 0.1.0 to 0.2.0
@@ -16,3 +16,3 @@ "use strict"; | ||
const MAX_COUNTER = 268435455; | ||
/** Returns a random uint generator based on available cryptographic RNG. */ | ||
/** Returns a random bit generator based on available cryptographic RNG. */ | ||
const detectRng = () => { | ||
@@ -51,3 +51,3 @@ if (typeof window !== "undefined" && window.crypto) { | ||
/** Returns a `k`-bit (cryptographically strong) random unsigned integer. */ | ||
this.getRandomUint = detectRng(); | ||
this.getRandomBits = detectRng(); | ||
} | ||
@@ -60,3 +60,3 @@ /** Generates a new SCRU128 ID object. */ | ||
this.tsLastGen = tsNow; | ||
this.counter = this.getRandomUint(28); | ||
this.counter = this.getRandomBits(28); | ||
} | ||
@@ -75,3 +75,3 @@ else if (++this.counter > MAX_COUNTER) { | ||
this.tsLastGen = tsNow; | ||
this.counter = this.getRandomUint(28); | ||
this.counter = this.getRandomBits(28); | ||
} | ||
@@ -81,5 +81,5 @@ // update perSecRandom | ||
this.tsLastSec = this.tsLastGen; | ||
this.perSecRandom = this.getRandomUint(16); | ||
this.perSecRandom = this.getRandomBits(24); | ||
} | ||
return new Identifier(this.tsLastGen - TIMESTAMP_EPOCH, this.counter, this.perSecRandom, this.getRandomUint(40)); | ||
return new Identifier(this.tsLastGen - TIMESTAMP_EPOCH, this.counter, this.perSecRandom, this.getRandomBits(32)); | ||
} | ||
@@ -92,4 +92,4 @@ } | ||
* @param counter - 28-bit counter. | ||
* @param perSecRandom - 16-bit per-second randomness. | ||
* @param perGenRandom - 40-bit per-generation randomness. | ||
* @param perSecRandom - 24-bit per-second randomness. | ||
* @param perGenRandom - 32-bit per-generation randomness. | ||
*/ | ||
@@ -111,4 +111,4 @@ constructor(timestamp, counter, perSecRandom, perGenRandom) { | ||
this.counter > MAX_COUNTER || | ||
this.perSecRandom > 0xffff || | ||
this.perGenRandom > 1099511627775) { | ||
this.perSecRandom > 16777215 || | ||
this.perGenRandom > 4294967295) { | ||
throw new RangeError("invalid field value"); | ||
@@ -120,6 +120,7 @@ } | ||
const h48 = this.timestamp * 0x10 + (this.counter >> 24); | ||
const m40 = (this.counter & 16777215) * 65536 + this.perSecRandom; | ||
const m40 = (this.counter & 16777215) * 65536 + (this.perSecRandom >> 8); | ||
const l40 = (this.perSecRandom & 0xff) * 4294967296 + this.perGenRandom; | ||
return (("000000000" + h48.toString(32)).slice(-10) + | ||
("0000000" + m40.toString(32)).slice(-8) + | ||
("0000000" + this.perGenRandom.toString(32)).slice(-8)).toUpperCase(); | ||
("0000000" + l40.toString(32)).slice(-8)).toUpperCase(); | ||
} | ||
@@ -134,3 +135,4 @@ /** Parses textual representation to create an object. */ | ||
const m40 = parseInt(m[2], 32); | ||
return new Identifier(Math.trunc(h48 / 0x10), (h48 % 0x10 << 24) | Math.trunc(m40 / 65536), m40 % 65536, parseInt(m[3], 32)); | ||
const l40 = parseInt(m[3], 32); | ||
return new Identifier(Math.trunc(h48 / 0x10), (h48 % 0x10 << 24) | Math.trunc(m40 / 65536), (m40 % 65536 << 8) | Math.trunc(l40 / 4294967296), l40 % 4294967296); | ||
} | ||
@@ -137,0 +139,0 @@ } |
{ | ||
"name": "scru128", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "SCRU128: Sortable, Clock and Random number-based Unique identifier", | ||
@@ -5,0 +5,0 @@ "main": "./dist/index.js", |
@@ -17,4 +17,4 @@ # SCRU128: Sortable, Clock and Random number-based Unique identifier | ||
console.log(scru128()); // e.g. "00PFDQ1L5D1SM1S3KUUCL9ABV2" | ||
console.log(scru128()); // e.g. "00PFDQ1L5D1SM1U3KUE2JOB6LC" | ||
console.log(scru128()); // e.g. "00PGHAJ3Q9VAJ7IU6PQBHBUAK4" | ||
console.log(scru128()); // e.g. "00PGHAJ3Q9VAJ7KU6PQ92NVBTV" | ||
``` | ||
@@ -26,2 +26,68 @@ | ||
## Design | ||
A SCRU128 ID is a 128-bit unsigned integer consisting of four terms: | ||
``` | ||
timestamp * 2^84 + counter * 2^56 + per_sec_random * 2^32 + per_gen_random | ||
``` | ||
Where: | ||
- `timestamp` is a 44-bit unix time in milliseconds biased by 50 years (i.e. | ||
milliseconds elapsed since 2020-01-01 00:00:00+00:00, ignoring leap seconds). | ||
- `counter` is a 28-bit counter incremented by one for each ID generated within | ||
the same `timestamp` and reset to a random number every millisecond. | ||
- `per_sec_random` is a 24-bit random number refreshed only once per second. | ||
- `per_gen_random` is a 32-bit random number renewed per generation of a new ID. | ||
This is essentially equivalent to allocating four unsigned integer fields to a | ||
128-bit space as follows in a big-endian system, and thus it is easier to be | ||
implemented with bitwise operators than arithmetic operators in many languages. | ||
| Bit numbers | Field name | Length | Data type | | ||
| ------------ | -------------- | ------- | ---------------- | | ||
| Msb 0 - 43 | timestamp | 44 bits | Unsigned integer | | ||
| Msb 44 - 71 | counter | 28 bits | Unsigned integer | | ||
| Msb 72 - 95 | per_sec_random | 24 bits | Unsigned integer | | ||
| Msb 96 - 127 | per_gen_random | 32 bits | Unsigned integer | | ||
### Layered randomness | ||
SCRU128 utilizes monotonic `counter` to guarantee the uniqueness of IDs with the | ||
same `timestamp`; however, this mechanism does not ensure the uniqueness of IDs | ||
generated by multiple generators that do not share a `counter` state. SCRU128 | ||
relies on random numbers to avoid such collisions. | ||
For a given length of random bits, the greater the number of random numbers | ||
generated, the higher the probability of collision. Therefore, SCRU128 gives | ||
some random bits a longer life to reduce the number of random number generation | ||
per a unit of time. As a result, even if each of multiple generators generates a | ||
million IDs at the same millisecond, no collision will occur as long as the | ||
random numbers generated only once per second (`per_sec_random`) differ. | ||
That being said, the `per_sec_random` field is refreshed every second to prevent | ||
potential attackers from using this field as a fingerprint to identify a | ||
generator. Also, the 32-bit `per_gen_random` field is reset to a new random | ||
number whenever an ID is generated to make sure the adjacent IDs generated | ||
within the same `timestamp` are not predictable. | ||
## Textual representation | ||
A SCRU128 ID is encoded to a string as a 128-bit unsigned integer denoted in the | ||
radix of 32 using the digits of `[0-9A-V]`, with leading zeros added to form a | ||
26-digit canonical representation. Converters for this simple base 32 notation | ||
are widely available in many languages; even if not, it is easily implemented | ||
with bitwise operations by translating each 5-bit group, from the least | ||
significant group to the most, into one digit of `[0-9A-V]`, from the least | ||
significant digit to the most. Since the three most significant bits are mapped | ||
to one of `[0-7]`, any numeral greater than `7VVVVVVVVVVVVVVVVVVVVVVVVV` is not | ||
a valid SCRU128 ID. | ||
Note that this is different from the encodings commonly referred to as _base32_ | ||
or _base32hex_ (e.g. [RFC 4648]), which read and translate 5-bit groups from the | ||
most significant one to the least. | ||
[rfc 4648]: https://www.ietf.org/rfc/rfc4648.txt | ||
## License | ||
@@ -41,1 +107,7 @@ | ||
specific language governing permissions and limitations under the License. | ||
## See also | ||
- [npm package](https://www.npmjs.com/package/scru128) | ||
- [API Documentation](https://scru128.github.io/javascript/docs/) | ||
- [Run tests on your browser](https://scru128.github.io/javascript/test/) |
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
23967
163
0
111