UNPKG

@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
# 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)