@btc-vision/btc-runtime
Version:
Bitcoin L1 Smart Contract Runtime for OP_NET. Build decentralized applications on Bitcoin using AssemblyScript and WebAssembly. Fully audited.
772 lines (609 loc) ⢠24.2 kB
Markdown
# Blockchain Environment
The `Blockchain` object is the primary interface for interacting with the OP_NET runtime. It provides access to block information, transaction context, storage operations, cryptographic functions, and cross-contract calls.
## Overview
```typescript
import { Blockchain } from '@btc-vision/btc-runtime/runtime';
```
The `Blockchain` object is globally available in all contracts and provides:
| Category | Description |
|----------|-------------|
| **Block Context** | Current block information (height, hash, timestamp) |
| **Transaction Context** | Sender, origin, inputs, outputs |
| **Contract Context** | Contract address, deployer, identity |
| **Storage** | Read/write persistent and transient storage |
| **Pointers** | Allocate storage slots |
| **Cross-Contract Calls** | Call other contracts |
| **Cryptography** | Hashing and signature verification |
| **Events** | Emit events |
## Block Context
Access information about the current block:
```typescript
// Current block number (height)
const blockNumber: u64 = Blockchain.block.number;
// Block number as u256
const blockNumberU256: u256 = Blockchain.block.numberU256;
// Block hash
const blockHash: Uint8Array = Blockchain.block.hash;
// Median time past (consensus timestamp)
const timestamp: u64 = Blockchain.block.medianTimestamp;
```
```mermaid
---
config:
theme: dark
---
flowchart LR
subgraph Block["Block Context"]
B1["Blockchain.block.number"] --> V1["u64"]
B2["Blockchain.block.numberU256"] --> V2["u256"]
B3["Blockchain.block.hash"] --> V3["Uint8Array (32 bytes)"]
B4["Blockchain.block.medianTimestamp"] --> V4["u64"]
end
```
### Solidity Comparison
| Solidity | OP_NET | Notes |
|----------|-------|-------|
| `block.number` | `Blockchain.block.number` | Current block height |
| `block.timestamp` | `Blockchain.block.medianTimestamp` | OP_NET uses Median Time Past |
| `blockhash(n)` | `Blockchain.getBlockHash(n)` | Historical block hash |
### Median Time Past
OP_NET uses **Median Time Past (MTP)** instead of raw block timestamps. MTP is the median of the last 11 block timestamps, providing more reliable time measurements that are resistant to miner manipulation.
```typescript
// Get current timestamp (median time past)
const currentTime: u64 = Blockchain.block.medianTimestamp;
// Time-based logic
const ONE_HOUR: u64 = 3600;
if (currentTime > this.deadline.value) {
throw new Revert('Deadline passed');
}
```
## Transaction Context
Access information about the current transaction:
```typescript
// Immediate caller (the address that called this contract)
const sender: Address = Blockchain.tx.sender;
// Original transaction signer (EOA that initiated the transaction)
// Note: origin is ExtendedAddress for quantum-resistant key support
const origin: ExtendedAddress = Blockchain.tx.origin;
// Transaction ID and hash
const txId: Uint8Array = Blockchain.tx.txId;
const txHash: Uint8Array = Blockchain.tx.hash;
// Transaction inputs and outputs (UTXOs)
const inputs: TransactionInput[] = Blockchain.tx.inputs;
const outputs: TransactionOutput[] = Blockchain.tx.outputs;
// Consensus rules for current transaction
const consensus: ConsensusRules = Blockchain.tx.consensus;
```
```mermaid
---
config:
theme: dark
---
flowchart LR
subgraph TX["Transaction Context"]
T1["Blockchain.tx.sender"] --> A1["Address"]
T2["Blockchain.tx.origin"] --> A2["ExtendedAddress"]
T3["Blockchain.tx.txId"] --> A3["Uint8Array"]
T4["Blockchain.tx.hash"] --> A4["Uint8Array"]
T5["Blockchain.tx.inputs"] --> A5["TransactionInput[]"]
T6["Blockchain.tx.outputs"] --> A6["TransactionOutput[]"]
end
```
### sender vs origin
This distinction is critical for security:
```
š¤ User (EOA) --> Contract A --> Contract B
origin=User origin=User
sender=User sender=ContractA
```
```typescript
// sender: The immediate caller (Address type)
// - Use for most authorization checks
// - Changes with each contract call
// origin: The original transaction signer (ExtendedAddress type)
// - Always the EOA that signed the transaction
// - Stays constant through the call chain
// - Supports quantum-resistant keys (ML-DSA)
// - Be careful: using origin can enable phishing attacks!
```
### Solidity Comparison
| Solidity | OP_NET | Notes |
|----------|-------|-------|
| `msg.sender` | `Blockchain.tx.sender` | Immediate caller |
| `tx.origin` | `Blockchain.tx.origin` | Original signer (ExtendedAddress) |
| `address(this)` | `Blockchain.contractAddress` | This contract's address |
| N/A | `Blockchain.contractDeployer` | Who deployed the contract |
### Contract Execution Context Flow
```mermaid
---
config:
theme: dark
---
sequenceDiagram
participant User as User/EOA
participant TX as Transaction Broadcast
participant VM as OP_NET VM
participant ContractA as Target Contract
participant ContractB as External Contract
participant Storage as Storage
Note over User,Storage: Transaction Initiation
User->>TX: Sign transaction
TX->>VM: Broadcast transaction
Note over User,Storage: OP_NET Runtime Initialization
VM->>VM: Initialize Blockchain Context
VM->>VM: Set tx.origin = User Address
VM->>VM: Set tx.sender = User Address
Note over User,Storage: Contract Loading
VM->>ContractA: Load Target Contract
VM->>VM: Set contractAddress
VM->>VM: Set contractDeployer
VM->>ContractA: Execute Contract Method
Note over User,Storage: Cross-Contract Calls (if any)
alt Makes Cross-Contract Call
ContractA->>VM: Request external call
VM->>VM: Update tx.sender to ContractA
VM->>ContractB: Call External Contract
ContractB->>VM: Return result
VM->>VM: Restore tx.sender
VM->>ContractA: Return result
end
Note over User,Storage: Transaction Finalization
ContractA->>VM: Complete Execution
VM->>Storage: Commit Storage Changes
VM->>VM: Emit Events
VM->>User: Transaction Complete
```
### Security Warning
```typescript
// DANGEROUS: Using origin for authorization
@method()
public withdraw(calldata: Calldata): BytesWriter {
// BAD: Allows phishing attacks through malicious contracts
if (Blockchain.tx.origin.equals(this.owner)) {
// ...
}
}
// SAFE: Using sender for authorization
@method()
public withdraw(calldata: Calldata): BytesWriter {
// GOOD: Only direct caller can access
if (Blockchain.tx.sender.equals(this.owner)) {
// ...
}
}
```
## Contract Context
Access contract metadata:
```typescript
// This contract's address
const address: Address = Blockchain.contractAddress;
// Contract deployer address
const deployer: Address = Blockchain.contractDeployer;
// Current contract instance
const contract: OP_NET = Blockchain.contract;
// Chain ID (for replay protection)
const chainId: Uint8Array = Blockchain.chainId;
// Protocol ID
const protocolId: Uint8Array = Blockchain.protocolId;
// Current network
const network: Networks = Blockchain.network;
// Dead address for burns
const deadAddress: ExtendedAddress = Blockchain.DEAD_ADDRESS;
```
```mermaid
---
config:
theme: dark
---
flowchart LR
subgraph Contract["Contract Context"]
C1["Blockchain.contractAddress"] --> V1["Address"]
C2["Blockchain.contractDeployer"] --> V2["Address"]
C3["Blockchain.contract"] --> V3["OP_NET"]
C4["Blockchain.chainId"] --> V4["Uint8Array"]
C5["Blockchain.network"] --> V5["Networks"]
C6["Blockchain.DEAD_ADDRESS"] --> V6["ExtendedAddress"]
end
```
## Storage Operations
### Persistent Storage
Direct storage access (low-level):
```typescript
import { encodePointer } from '@btc-vision/btc-runtime/runtime';
// Create storage key from pointer and subPointer
const pointerHash = encodePointer(pointer, subPointer);
// Write to storage
Blockchain.setStorageAt(pointerHash, value.toUint8Array(true));
// Read from storage
const stored = Blockchain.getStorageAt(pointerHash);
const value = u256.fromUint8ArrayBE(stored);
// Check if storage slot has value
const hasValue: bool = Blockchain.hasStorageAt(pointerHash);
```
### Transient Storage
Transient storage is cleared after each transaction (useful for reentrancy guards):
```typescript
// Write to transient storage
Blockchain.setTransientStorageAt(pointerHash, value);
// Read from transient storage
const transientValue = Blockchain.getTransientStorageAt(pointerHash);
// Check if transient storage slot has value
const hasTransient: bool = Blockchain.hasTransientStorageAt(pointerHash);
```
**Warning:** Transient storage is NOT CURRENTLY ENABLED IN PRODUCTION. It is experimental and only available in the testing framework.
**Note:** For most use cases, use the typed storage classes like `StoredU256`, `StoredString`, etc. Direct storage access is for advanced scenarios.
See [Storage System](./storage-system.md) for detailed information.
## Pointer Allocation
Allocate storage pointers for your contract:
```typescript
// Get the next available pointer
const myPointer: u16 = Blockchain.nextPointer;
// Use it for storage
private balancePointer: u16 = Blockchain.nextPointer;
private allowancePointer: u16 = Blockchain.nextPointer;
```
Each `nextPointer` call returns a unique `u16` value. Pointers are allocated sequentially starting from 1. Limited to 65,535 storage slots per contract.
See [Pointers](./pointers.md) for more details.
## Cross-Contract Calls
Call other contracts:
```typescript
import { Blockchain, Address, BytesWriter, CallResult } from '@btc-vision/btc-runtime/runtime';
// Prepare the call
const targetContract: Address = /* ... */;
const calldata: BytesWriter = /* encoded call data */;
const stopOnFailure: bool = true;
// Make the call
const result: CallResult = Blockchain.call(targetContract, calldata, stopOnFailure);
// Handle the result
if (result.success) {
const returnData = result.data; // BytesReader
// Process return data...
} else {
throw new Revert('External call failed');
}
```
### Call Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `destinationContract` | `Address` | Contract to call |
| `calldata` | `BytesWriter` | Encoded function call |
| `stopExecutionOnFailure` | `boolean` | If true (default), revert entire transaction on failure |
### Cross-Contract Call Sequence
```mermaid
---
config:
theme: dark
---
sequenceDiagram
participant UserEOA as š¤ User (EOA)
participant ContractA as Contract A
participant Blockchain as Blockchain Singleton
participant ContractB as Contract B
participant Storage as Bitcoin L1 Storage
UserEOA->>ContractA: Call methodA()
Note over ContractA: tx.sender = UserEOA<br/>tx.origin = UserEOA
ContractA->>ContractA: Prepare calldata for Contract B
ContractA->>Blockchain: call(ContractB, calldata, stopOnFailure)
Blockchain->>Blockchain: Save current context
Blockchain->>Blockchain: Update tx.sender = ContractA
Note over Blockchain: tx.origin remains UserEOA
Blockchain->>ContractB: Execute methodB(calldata)
Note over ContractB: tx.sender = ContractA<br/>tx.origin = UserEOA
ContractB->>Storage: Read/Write State
Storage-->>ContractB: State Data
ContractB-->>Blockchain: Return CallResult{success, data}
Blockchain->>Blockchain: Restore previous context
Blockchain-->>ContractA: Return CallResult
ContractA->>ContractA: Process return data
alt stopOnFailure = true AND success = false
ContractA->>UserEOA: Revert entire transaction
else success = true
ContractA->>Storage: Commit changes
ContractA-->>UserEOA: Return success
end
```
### Solidity Comparison
```solidity
// Solidity
(bool success, bytes memory data) = target.call(calldata);
// OP_NET
const result = Blockchain.call(target, calldata, true);
// result.success: boolean
// result.data: BytesReader
```
See [Cross-Contract Calls](../advanced/cross-contract-calls.md) for advanced usage.
## Contract Deployment
Deploy new contracts from existing templates:
```typescript
// Deploy a new contract using an existing contract as template
const newContractAddress: Address = Blockchain.deployContractFromExisting(
templateAddress, // Address of existing contract to clone
salt, // u256 salt for deterministic address
constructorData // BytesWriter with constructor parameters
);
```
This uses CREATE2-style deterministic addressing: same salt + template = same address.
## Cryptographic Operations
### SHA256 Hashing
```typescript
// Single SHA256
const hash: Uint8Array = Blockchain.sha256(data);
// Double SHA256 (hash256 - common in Bitcoin)
const doubleHash: Uint8Array = Blockchain.hash256(data);
```
### Signature Verification
OP_NET supports Schnorr, ECDSA (secp256k1), and quantum-resistant ML-DSA signatures:
```typescript
import { SignaturesMethods } from '@btc-vision/btc-runtime/runtime';
// Recommended: Use consensus-aware verification
// Automatically selects Schnorr or ML-DSA based on consensus rules
const isValid: bool = Blockchain.verifySignature(
address, // ExtendedAddress
signature, // Uint8Array
hash // Uint8Array (32 bytes)
);
// Force ML-DSA verification
const isValidMLDSA: bool = Blockchain.verifySignature(
address,
signature,
hash,
SignaturesMethods.MLDSA // Force quantum-resistant ML-DSA
);
```
```mermaid
---
config:
theme: dark
---
flowchart LR
subgraph Verify["Blockchain.verifySignature()"]
Check{"signatureType?"}
Check -->|Schnorr| SchnorrCheck{"unsafeSignaturesAllowed?"}
SchnorrCheck -->|Yes| Schnorr["Schnorr Verification<br/>(64-byte signature)"]
SchnorrCheck -->|No| MLDSA
Check -->|MLDSA| MLDSA["ML-DSA Verification<br/>(ML-DSA-44, Level2)"]
Check -->|ECDSA| ECDSAErr["Error: Use dedicated<br/>ECDSA methods"]
end
Input["address, signature, hash, signatureType"] --> Check
Schnorr --> Result["boolean"]
MLDSA --> Result
```
#### ECDSA Verification (Deprecated)
```typescript
// Ethereum ecrecover model (65-byte signature: r32 || s32 || v1)
const isValid: bool = Blockchain.verifyECDSASignature(
publicKey, // secp256k1 key (33, 64, or 65 bytes)
signature, // 65-byte Ethereum ECDSA signature
hash // 32-byte message hash
);
// Bitcoin direct verify model (64-byte compact signature: r32 || s32)
const isValid: bool = Blockchain.verifyBitcoinECDSASignature(
publicKey, // secp256k1 key (33, 64, or 65 bytes)
signature, // 64-byte compact ECDSA signature
hash // 32-byte message hash
);
```
> **Warning:** ECDSA methods are deprecated and only available when `UNSAFE_QUANTUM_SIGNATURES_ALLOWED` consensus flag is set. Migrate to ML-DSA for quantum security.
#### Legacy Methods (Deprecated)
```typescript
// Verify Schnorr signature directly (deprecated)
const isValid: bool = Blockchain.verifySchnorrSignature(
publicKey, // ExtendedAddress
signature, // Uint8Array (64 bytes)
hash // Uint8Array (32 bytes)
);
// Verify ML-DSA signature with specific security level
const isValidQuantum: bool = Blockchain.verifyMLDSASignature(
MLDSASecurityLevel.Level2, // Level2, Level3, or Level5
publicKey, // Uint8Array (size depends on level)
signature, // Uint8Array (size depends on level)
hash // Uint8Array (32 bytes)
);
```
ML-DSA Security Levels:
- **Level2 (ML-DSA-44)**: 1312-byte public key, 2420-byte signature
- **Level3 (ML-DSA-65)**: 1952-byte public key, 3309-byte signature
- **Level5 (ML-DSA-87)**: 2592-byte public key, 4627-byte signature
### Keccak-256 Hashing
OP_NET includes an Ethereum-compatible Keccak-256 implementation (original Keccak, not NIST SHA-3):
```typescript
import { keccak256, keccak256Concat, functionSelector, ethAddressFromPubKey } from '@btc-vision/btc-runtime/runtime';
// Basic keccak256 hash
const hash: Uint8Array = keccak256(data); // 32 bytes
// Hash concatenated byte arrays
const hash2: Uint8Array = keccak256Concat(a, b);
// Ethereum function selector (4 bytes)
const sel: Uint8Array = functionSelector('transfer(address,uint256)');
// Derive Ethereum address from 64-byte uncompressed public key
const addr: Uint8Array = ethAddressFromPubKey(pubkey64); // 20 bytes
```
See [Signature Verification](../advanced/signature-verification.md) for details.
## Utility Methods
### Account Type Check
```typescript
// Check if an address is a contract
const isContract: bool = Blockchain.isContract(address);
// Get account type code (0 = EOA, >0 = contract type)
const accountType: u32 = Blockchain.getAccountType(address);
```
### Bitcoin Address Validation
```typescript
// Validate a Bitcoin address for the current network
const isValid: bool = Blockchain.validateBitcoinAddress(addressString);
```
### Block Hash Lookup
```typescript
// Get historical block hash (limited to ~256 recent blocks)
const oldBlockHash: Uint8Array = Blockchain.getBlockHash(blockNumber);
```
### Logging (Testing Only)
```typescript
// Log debug messages (only works in unit testing framework)
Blockchain.log("Debug message");
```
**Warning:** `log()` is ONLY available in the unit testing framework. It will fail in production or testnet environments.
## Event Emission
Emit events for off-chain indexing:
```typescript
import { NetEvent } from '@btc-vision/btc-runtime/runtime';
// Emit an event
Blockchain.emit(new TransferEvent(from, to, amount));
```
See [Events](./events.md) for complete event documentation.
## Blockchain Singleton Architecture
```mermaid
---
config:
theme: dark
---
classDiagram
class BlockchainEnvironment {
<<singleton>>
+DEAD_ADDRESS: ExtendedAddress
+network: Networks
+block: Block
+tx: Transaction
+contract: OP_NET
+nextPointer: u16
+contractDeployer: Address
+contractAddress: Address
+chainId: Uint8Array
+protocolId: Uint8Array
+call(destination, calldata, stopOnFailure) CallResult
+deployContractFromExisting(address, salt, calldata) Address
+getStorageAt(pointerHash) Uint8Array
+setStorageAt(pointerHash, value) void
+hasStorageAt(pointerHash) bool
+getTransientStorageAt(pointerHash) Uint8Array
+setTransientStorageAt(pointerHash, value) void
+hasTransientStorageAt(pointerHash) bool
+sha256(buffer) Uint8Array
+hash256(buffer) Uint8Array
+verifySignature(address, sig, hash, signatureType?) bool
+verifyECDSASignature(pubKey, sig, hash) bool
+verifyBitcoinECDSASignature(pubKey, sig, hash) bool
+verifySchnorrSignature(pubKey, sig, hash) bool
+verifyMLDSASignature(level, pubKey, sig, hash) bool
+isContract(address) bool
+getAccountType(address) u32
+validateBitcoinAddress(address) bool
+getBlockHash(blockNumber) Uint8Array
+emit(event) void
+log(data) void
}
class Block {
+hash: Uint8Array
+number: u64
+numberU256: u256
+medianTimestamp: u64
}
class Transaction {
+sender: Address
+origin: ExtendedAddress
+txId: Uint8Array
+hash: Uint8Array
+inputs: TransactionInput[]
+outputs: TransactionOutput[]
+consensus: ConsensusRules
}
class CallResult {
+success: boolean
+data: BytesReader
}
BlockchainEnvironment --> Block: block
BlockchainEnvironment --> Transaction: tx
BlockchainEnvironment ..> CallResult: returns
```
## Example: Using Blockchain in a Contract
```typescript
import { u256 } from '@btc-vision/as-bignum/assembly';
import {
OP_NET,
Blockchain,
Calldata,
BytesWriter,
StoredU256,
Revert,
EMPTY_POINTER,
ABIDataTypes,
} from '@btc-vision/btc-runtime/runtime';
@final
export class MyContract extends OP_NET {
// Storage pointer declaration
private readonly lastUpdatePointer: u16 = Blockchain.nextPointer;
private readonly lastUpdate: StoredU256 = new StoredU256(
this.lastUpdatePointer,
EMPTY_POINTER
);
public constructor() {
super();
}
public override onDeployment(_calldata: Calldata): void {
// Store deployment block
this.lastUpdate.value = u256.fromU64(Blockchain.block.number);
}
@method()
public doSomething(calldata: Calldata): BytesWriter {
// Access control
this.onlyDeployer(Blockchain.tx.sender);
// Time check
const now = Blockchain.block.medianTimestamp;
const lastBlock = this.lastUpdate.value.toU64();
const currentBlock = Blockchain.block.number;
// Must wait at least 10 blocks
if (currentBlock - lastBlock < 10) {
throw new Revert('Must wait 10 blocks');
}
// Update timestamp
this.lastUpdate.value = u256.fromU64(currentBlock);
return new BytesWriter(0);
}
}
```
## Summary
| Property/Method | Returns | Description |
|-----------------|---------|-------------|
| `Blockchain.block.number` | `u64` | Current block height |
| `Blockchain.block.numberU256` | `u256` | Current block height as u256 |
| `Blockchain.block.hash` | `Uint8Array` | Current block hash |
| `Blockchain.block.medianTimestamp` | `u64` | Median time past |
| `Blockchain.tx.sender` | `Address` | Immediate caller |
| `Blockchain.tx.origin` | `ExtendedAddress` | Original signer |
| `Blockchain.tx.txId` | `Uint8Array` | Transaction ID |
| `Blockchain.tx.hash` | `Uint8Array` | Transaction hash |
| `Blockchain.tx.inputs` | `TransactionInput[]` | Transaction inputs |
| `Blockchain.tx.outputs` | `TransactionOutput[]` | Transaction outputs |
| `Blockchain.tx.consensus` | `ConsensusRules` | Consensus rules |
| `Blockchain.contract` | `OP_NET` | Current contract instance |
| `Blockchain.contractAddress` | `Address` | This contract's address |
| `Blockchain.contractDeployer` | `Address` | Contract deployer |
| `Blockchain.chainId` | `Uint8Array` | Chain identifier |
| `Blockchain.protocolId` | `Uint8Array` | Protocol identifier |
| `Blockchain.network` | `Networks` | Current network |
| `Blockchain.DEAD_ADDRESS` | `ExtendedAddress` | Burn address |
| `Blockchain.nextPointer` | `u16` | Next storage pointer |
| `Blockchain.call()` | `CallResult` | Cross-contract call |
| `Blockchain.deployContractFromExisting()` | `Address` | Deploy new contract |
| `Blockchain.getStorageAt()` | `Uint8Array` | Read persistent storage |
| `Blockchain.setStorageAt()` | `void` | Write persistent storage |
| `Blockchain.hasStorageAt()` | `bool` | Check storage existence |
| `Blockchain.getTransientStorageAt()` | `Uint8Array` | Read transient storage |
| `Blockchain.setTransientStorageAt()` | `void` | Write transient storage |
| `Blockchain.hasTransientStorageAt()` | `bool` | Check transient storage existence |
| `Blockchain.sha256()` | `Uint8Array` | SHA256 hash |
| `Blockchain.hash256()` | `Uint8Array` | Double SHA256 |
| `Blockchain.verifySignature()` | `bool` | Consensus-aware signature verification |
| `Blockchain.verifyECDSASignature()` | `bool` | ECDSA Ethereum ecrecover verification (deprecated) |
| `Blockchain.verifyBitcoinECDSASignature()` | `bool` | ECDSA Bitcoin direct verification (deprecated) |
| `Blockchain.verifySchnorrSignature()` | `bool` | Schnorr verification (deprecated) |
| `Blockchain.verifyMLDSASignature()` | `bool` | ML-DSA verification |
| `Blockchain.isContract()` | `bool` | Check if address is contract |
| `Blockchain.getAccountType()` | `u32` | Get account type code |
| `Blockchain.validateBitcoinAddress()` | `bool` | Validate Bitcoin address |
| `Blockchain.getBlockHash()` | `Uint8Array` | Historical block hash |
| `Blockchain.emit()` | `void` | Emit event |
| `Blockchain.log()` | `void` | Debug logging (testing only) |
---
**Navigation:**
- Previous: [Project Structure](../getting-started/project-structure.md)
- Next: [Storage System](./storage-system.md)