@btc-vision/btc-runtime
Version:
Bitcoin L1 Smart Contract Runtime for OP_NET. Build decentralized applications on Bitcoin using AssemblyScript and WebAssembly. Fully audited.
146 lines (116 loc) • 4.11 kB
text/typescript
import { ExtendedAddress } from '../types/ExtendedAddress';
import { Revert } from '../types/Revert';
import { IMap } from './Map';
/**
* A map implementation using ExtendedAddress (64 bytes) as keys.
* Uses hyper-optimized search with prefix filtering for performance.
*/
export class ExtendedAddressMap<V> implements IMap<ExtendedAddress, V> {
protected _keys: ExtendedAddress[] = [];
protected _values: V[] = [];
// CACHE: Stores the index of the last successful lookup to make repeated access O(1)
private _lastIndex: i32 = -1;
public get size(): i32 {
return this._keys.length;
}
public keys(): ExtendedAddress[] {
return this._keys;
}
public values(): V[] {
return this._values;
}
public set(key: ExtendedAddress, value: V): this {
const index = this.indexOf(key);
if (index === -1) {
this._keys.push(key);
this._values.push(value);
this._lastIndex = this._keys.length - 1;
} else {
unchecked((this._values[index] = value));
this._lastIndex = index;
}
return this;
}
/**
* HYPER-OPTIMIZED SEARCH
* Compares both the ML-DSA key hash (inherited) and tweakedPublicKey.
*/
public indexOf(searchKey: ExtendedAddress): i32 {
if (this.isLastIndex(searchKey)) {
return this._lastIndex;
}
const len = this._keys.length;
if (len === 0) return -1;
const searchMldsaData = searchKey.dataStart;
const searchTweakedData = searchKey.tweakedPublicKey.dataStart;
// Loop Backwards (finding most recently added items first)
for (let i = len - 1; i >= 0; i--) {
const key = unchecked(this._keys[i]);
// Quick prefix check on ML-DSA key hash (first 8 bytes)
if (load<u64>(key.dataStart) !== load<u64>(searchMldsaData)) continue;
// Quick prefix check on tweaked public key (first 8 bytes)
if (load<u64>(key.tweakedPublicKey.dataStart) !== load<u64>(searchTweakedData))
continue;
// Full comparison of ML-DSA key hash (32 bytes)
if (memory.compare(key.dataStart, searchMldsaData, 32) !== 0) continue;
// Full comparison of tweaked public key (32 bytes)
if (memory.compare(key.tweakedPublicKey.dataStart, searchTweakedData, 32) === 0) {
this._lastIndex = i;
return i;
}
}
return -1;
}
public has(key: ExtendedAddress): bool {
return this.indexOf(key) !== -1;
}
public get(key: ExtendedAddress): V {
const index = this.indexOf(key);
if (index === -1) {
throw new Revert('Key not found in map');
}
return unchecked(this._values[index]);
}
public delete(key: ExtendedAddress): bool {
const index = this.indexOf(key);
if (index === -1) return false;
this._keys.splice(index, 1);
this._values.splice(index, 1);
this._lastIndex = -1;
return true;
}
public clear(): void {
this._keys = [];
this._values = [];
this._lastIndex = -1;
}
public toString(): string {
return `ExtendedAddressMap(size=${this._keys.length})`;
}
private isLastIndex(key: ExtendedAddress): bool {
if (this._lastIndex !== -1) {
const cachedKey = unchecked(this._keys[this._lastIndex]);
// Check ML-DSA key hash equality
if (memory.compare(cachedKey.dataStart, key.dataStart, 32) !== 0) {
return false;
}
// Check tweaked public key equality
if (
memory.compare(
cachedKey.tweakedPublicKey.dataStart,
key.tweakedPublicKey.dataStart,
32,
) === 0
) {
return true;
}
}
return false;
}
}