Skip to content

Commit bc0ab58

Browse files
committed
Faster BN conversions
1 parent e2e673c commit bc0ab58

File tree

4 files changed

+77
-55
lines changed

4 files changed

+77
-55
lines changed

src/primitives/BigNumber.ts

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
// @ts-nocheck
22
import ReductionContext from './ReductionContext.js'
33

4+
const BufferCtor =
5+
typeof globalThis !== 'undefined' ? (globalThis as any).Buffer : undefined
6+
const CAN_USE_BUFFER =
7+
BufferCtor != null && typeof BufferCtor.from === 'function'
8+
const HEX_CHAR_TO_VALUE = new Int8Array(256).fill(-1)
9+
for (let i = 0; i < 10; i++) {
10+
HEX_CHAR_TO_VALUE[48 + i] = i // '0'-'9'
11+
}
12+
for (let i = 0; i < 6; i++) {
13+
HEX_CHAR_TO_VALUE[65 + i] = 10 + i // 'A'-'F'
14+
HEX_CHAR_TO_VALUE[97 + i] = 10 + i // 'a'-'f'
15+
}
16+
417
/**
518
* JavaScript numbers are only precise up to 53 bits. Since Bitcoin relies on
619
* 256-bit cryptography, this BigNumber class enables operations on larger
@@ -1059,31 +1072,30 @@ export default class BigNumber {
10591072
static fromSm (bytes: number[], endian: 'big' | 'little' = 'big'): BigNumber {
10601073
if (bytes.length === 0) return new BigNumber(0n)
10611074

1075+
const beBytes = bytes.slice()
1076+
if (endian === 'little') {
1077+
beBytes.reverse()
1078+
}
10621079
let sign: 0 | 1 = 0
1063-
let hex = ''
1080+
if (beBytes.length > 0 && (beBytes[0] & 0x80) !== 0) {
1081+
sign = 1
1082+
beBytes[0] &= 0x7f
1083+
}
10641084

1065-
if (endian === 'little') {
1066-
const last = bytes.length - 1
1067-
let firstByte = bytes[last]
1068-
if ((firstByte & 0x80) !== 0) { sign = 1; firstByte &= 0x7f }
1069-
hex += (firstByte < 16 ? '0' : '') + firstByte.toString(16)
1070-
for (let i = last - 1; i >= 0; i--) {
1071-
const b = bytes[i]
1072-
hex += (b < 16 ? '0' : '') + b.toString(16)
1073-
}
1085+
let magnitude = 0n
1086+
if (CAN_USE_BUFFER) {
1087+
const hex = BufferCtor.from(beBytes).toString('hex') as string
1088+
magnitude = hex.length === 0 ? 0n : BigInt('0x' + hex)
10741089
} else {
1075-
let firstByte = bytes[0]
1076-
if ((firstByte & 0x80) !== 0) { sign = 1; firstByte &= 0x7f }
1077-
hex += (firstByte < 16 ? '0' : '') + firstByte.toString(16)
1078-
for (let i = 1; i < bytes.length; i++) {
1079-
const b = bytes[i]
1080-
hex += (b < 16 ? '0' : '') + b.toString(16)
1090+
let hex = ''
1091+
for (const byte of beBytes) {
1092+
hex += byte < 16 ? '0' + byte.toString(16) : byte.toString(16)
10811093
}
1094+
magnitude = hex.length === 0 ? 0n : BigInt('0x' + hex)
10821095
}
10831096

1084-
const mag = hex === '' ? 0n : BigInt('0x' + hex)
10851097
const r = new BigNumber(0n)
1086-
r._initializeState(mag, sign)
1098+
r._initializeState(magnitude, sign)
10871099
return r
10881100
}
10891101

@@ -1105,17 +1117,26 @@ export default class BigNumber {
11051117
const byteLen = hex.length / 2
11061118
const bytes = new Array(byteLen)
11071119
for (let i = 0, j = 0; i < hex.length; i += 2) {
1108-
bytes[j++] = parseInt(hex.slice(i, i + 2), 16)
1120+
const high = HEX_CHAR_TO_VALUE[hex.charCodeAt(i)]
1121+
const low = HEX_CHAR_TO_VALUE[hex.charCodeAt(i + 1)]
1122+
bytes[j++] = ((high & 0xf) << 4) | (low & 0xf)
11091123
}
11101124

1125+
let result: number[]
11111126
if (this._sign === 1) {
1112-
if ((bytes[0] & 0x80) !== 0) bytes.unshift(0x80)
1113-
else bytes[0] |= 0x80
1127+
if ((bytes[0] & 0x80) !== 0) {
1128+
result = [0x80, ...bytes]
1129+
} else {
1130+
result = bytes.slice()
1131+
result[0] |= 0x80
1132+
}
11141133
} else if ((bytes[0] & 0x80) !== 0) {
1115-
bytes.unshift(0x00)
1134+
result = [0x00, ...bytes]
1135+
} else {
1136+
result = bytes.slice()
11161137
}
11171138

1118-
return endian === 'little' ? bytes.reverse() : bytes
1139+
return endian === 'little' ? result.reverse() : result
11191140
}
11201141

11211142
/**

src/primitives/SymmetricKey.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,22 @@ export default class SymmetricKey extends BigNumber {
4343
encrypt (msg: number[] | string, enc?: 'hex'): string | number[] {
4444
const iv = Random(32)
4545
msg = toArray(msg, enc)
46+
const keyBytes = this.toArray('be', 32)
4647
const { result, authenticationTag } = AESGCM(
4748
msg,
4849
[],
4950
iv,
50-
this.toArray('be', 32)
51+
keyBytes
5152
)
52-
return encode([...iv, ...result, ...authenticationTag], enc)
53+
const totalLength = iv.length + result.length + authenticationTag.length
54+
const combined = new Array(totalLength)
55+
let offset = 0
56+
for (const chunk of [iv, result, authenticationTag]) {
57+
for (let i = 0; i < chunk.length; i++) {
58+
combined[offset++] = chunk[i]
59+
}
60+
}
61+
return encode(combined, enc)
5362
}
5463

5564
/**
@@ -71,15 +80,15 @@ export default class SymmetricKey extends BigNumber {
7180
decrypt (msg: number[] | string, enc?: 'hex' | 'utf8'): string | number[] {
7281
msg = toArray(msg, enc)
7382
const iv = msg.slice(0, 32)
74-
const ciphertextWithTag = msg.slice(32)
75-
const messageTag = ciphertextWithTag.slice(-16)
76-
const ciphertext = ciphertextWithTag.slice(0, -16)
83+
const tagStart = msg.length - 16
84+
const ciphertext = msg.slice(32, tagStart)
85+
const messageTag = msg.slice(tagStart)
7786
const result = AESGCMDecrypt(
7887
ciphertext,
7988
[],
8089
iv,
8190
messageTag,
82-
this.toArray()
91+
this.toArray('be', 32)
8392
)
8493
if (result === null) {
8594
throw new Error('Decryption failed!')

src/primitives/utils.ts

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -449,8 +449,15 @@ export class Writer {
449449
const ret = new Array(totalLength)
450450
let offset = 0
451451
for (const buf of this.bufs) {
452-
for (const value of buf) {
453-
ret[offset++] = value
452+
if (buf instanceof Uint8Array) {
453+
for (let i = 0; i < buf.length; i++) {
454+
ret[offset++] = buf[i]
455+
}
456+
} else {
457+
const arr = buf as number[]
458+
for (let i = 0; i < arr.length; i++) {
459+
ret[offset++] = arr[i]
460+
}
454461
}
455462
}
456463
return ret
@@ -467,14 +474,12 @@ export class Writer {
467474
for (let i = 0; i < buf2.length; i++) {
468475
buf2[i] = buf[buf.length - 1 - i]
469476
}
470-
this.bufs.push(buf2)
471-
this.length += buf2.length
472-
return this
477+
return this.write(buf2)
473478
}
474479

475480
writeUInt8 (n: number): this {
476481
const buf = new Array(1)
477-
buf[0] = n
482+
buf[0] = n & 0xff
478483
this.write(buf)
479484
return this
480485
}
@@ -491,9 +496,7 @@ export class Writer {
491496
(n >> 8) & 0xff, // shift right 8 bits to get the high byte
492497
n & 0xff // low byte is just the last 8 bits
493498
]
494-
this.bufs.push(buf)
495-
this.length += 2
496-
return this
499+
return this.write(buf)
497500
}
498501

499502
writeInt16BE (n: number): this {
@@ -505,9 +508,7 @@ export class Writer {
505508
n & 0xff, // low byte is just the last 8 bits
506509
(n >> 8) & 0xff // shift right 8 bits to get the high byte
507510
]
508-
this.bufs.push(buf)
509-
this.length += 2
510-
return this
511+
return this.write(buf)
511512
}
512513

513514
writeInt16LE (n: number): this {
@@ -521,9 +522,7 @@ export class Writer {
521522
(n >> 8) & 0xff,
522523
n & 0xff // lowest byte
523524
]
524-
this.bufs.push(buf)
525-
this.length += 4
526-
return this
525+
return this.write(buf)
527526
}
528527

529528
writeInt32BE (n: number): this {
@@ -537,9 +536,7 @@ export class Writer {
537536
(n >> 16) & 0xff,
538537
(n >> 24) & 0xff // highest byte
539538
]
540-
this.bufs.push(buf)
541-
this.length += 4
542-
return this
539+
return this.write(buf)
543540
}
544541

545542
writeInt32LE (n: number): this {

src/transaction/Transaction.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,14 +1047,9 @@ export default class Transaction {
10471047
* @throws Error if there are any missing sourceTransactions unless `allowPartial` is true.
10481048
*/
10491049
toAtomicBEEF (allowPartial?: boolean): number[] {
1050-
const writer = new Writer()
1051-
// Write the Atomic BEEF prefix
1052-
writer.writeUInt32LE(0x01010101)
1053-
// Write the subject TXID (big-endian)
1054-
writer.write(this.hash())
1055-
// Append the BEEF data
1050+
const prefix = [1, 1, 1, 1]
1051+
const txHash = this.hash() as number[]
10561052
const beefData = this.toBEEF(allowPartial)
1057-
writer.write(beefData)
1058-
return writer.toArray()
1053+
return prefix.concat(txHash, beefData)
10591054
}
10601055
}

0 commit comments

Comments
 (0)