Duration: 45 minutes
Prerequisites: Node.js, basic TypeScript knowledge, completed “Your First BSV Transaction” tutorial
Learning Goals:
ProtoWallet is a lightweight wallet implementation designed for development and testing scenarios. Unlike full wallets, ProtoWallet focuses purely on cryptographic operations without blockchain interaction, making it perfect for:
In this tutorial, you’ll create a development toolkit using ProtoWallet that includes:
import { ProtoWallet, PrivateKey } from '@bsv/sdk'
async function createProtoWallet() {
// Create with a random private key
const randomWallet = new ProtoWallet(PrivateKey.fromRandom())
// Create with a specific private key
const privateKey = PrivateKey.fromRandom()
const specificWallet = new ProtoWallet(privateKey)
// Create with 'anyone' key (for testing)
const anyoneWallet = new ProtoWallet('anyone')
console.log('ProtoWallet instances created successfully')
return { randomWallet, specificWallet, anyoneWallet }
}
createProtoWallet().catch(console.error)
import { ProtoWallet } from '@bsv/sdk'
async function demonstratePublicKeys() {
const wallet = new ProtoWallet(PrivateKey.fromRandom())
// Get identity public key
const { publicKey: identityKey } = await wallet.getPublicKey({
identityKey: true
})
console.log('Identity Key:', identityKey)
// Get derived public key for a protocol
const { publicKey: protocolKey } = await wallet.getPublicKey({
protocolID: [1, 'my-app'],
keyID: 'user-signing-key'
})
console.log('Protocol Key:', protocolKey)
// Get public key for counterparty communication
const { publicKey: counterpartyKey } = await wallet.getPublicKey({
protocolID: [1, 'messaging'],
keyID: 'chat-key',
counterparty: identityKey
})
console.log('Counterparty Key:', counterpartyKey)
}
demonstratePublicKeys().catch(console.error)
import { ProtoWallet, Utils } from '@bsv/sdk'
async function demonstrateSignatures() {
const wallet = new ProtoWallet(PrivateKey.fromRandom())
// Message to sign
const message = 'Hello, BSV development!'
const messageBytes = Utils.toArray(message, 'utf8')
// Create signature
const { signature } = await wallet.createSignature({
data: messageBytes,
protocolID: [1, 'document signing'],
keyID: 'doc-key',
counterparty: 'self'
})
console.log('Message:', message)
console.log('Signature:', Utils.toBase64(signature))
// Verify signature
const { valid } = await wallet.verifySignature({
data: messageBytes,
signature,
protocolID: [1, 'document signing'],
keyID: 'doc-key',
counterparty: 'self'
})
console.log('Signature valid:', valid)
return { message, signature, valid }
}
demonstrateSignatures().catch(console.error)
import { ProtoWallet, Utils } from '@bsv/sdk'
class DocumentSigner {
private wallet: ProtoWallet
constructor() {
this.wallet = new ProtoWallet(PrivateKey.fromRandom())
}
async signDocument(content: string, documentId: string): Promise<{
signature: number[]
publicKey: string
timestamp: number
}> {
const timestamp = Date.now()
const documentData = {
content,
documentId,
timestamp
}
const dataToSign = Utils.toArray(JSON.stringify(documentData), 'utf8')
const { signature } = await this.wallet.createSignature({
data: dataToSign,
protocolID: [1, 'document system'],
keyID: `doc-${documentId}`,
counterparty: 'self'
})
const { publicKey } = await this.wallet.getPublicKey({
protocolID: [1, 'document system'],
keyID: `doc-${documentId}`
})
return { signature, publicKey, timestamp }
}
async verifyDocument(
content: string,
documentId: string,
signature: number[],
timestamp: number
): Promise<boolean> {
const documentData = {
content,
documentId,
timestamp
}
const dataToVerify = Utils.toArray(JSON.stringify(documentData), 'utf8')
const { valid } = await this.wallet.verifySignature({
data: dataToVerify,
signature,
protocolID: [1, 'document system'],
keyID: `doc-${documentId}`,
counterparty: 'self'
})
return valid
}
}
async function demonstrateDocumentSigning() {
const signer = new DocumentSigner()
const document = {
content: 'This is a confidential document requiring digital signature.',
id: 'contract-2024-001'
}
// Sign document
const signatureData = await signer.signDocument(document.content, document.id)
console.log('Document signed:', {
documentId: document.id,
publicKey: signatureData.publicKey,
timestamp: new Date(signatureData.timestamp).toISOString()
})
// Verify document
const isValid = await signer.verifyDocument(
document.content,
document.id,
signatureData.signature,
signatureData.timestamp
)
console.log('Document verification:', isValid ? 'VALID' : 'INVALID')
return { signatureData, isValid }
}
demonstrateDocumentSigning().catch(console.error)
import { ProtoWallet, Utils } from '@bsv/sdk'
async function demonstrateEncryption() {
const wallet = new ProtoWallet(PrivateKey.fromRandom())
// Data to encrypt
const secretMessage = 'This is confidential development data'
const plaintext = Utils.toArray(secretMessage, 'utf8')
// Encrypt data
const { ciphertext } = await wallet.encrypt({
plaintext,
protocolID: [1, 'secure storage'],
keyID: 'data-encryption-key'
})
console.log('Original message:', secretMessage)
console.log('Encrypted (base64):', Utils.toBase64(ciphertext))
// Decrypt data
const { plaintext: decrypted } = await wallet.decrypt({
ciphertext,
protocolID: [1, 'secure storage'],
keyID: 'data-encryption-key'
})
const decryptedMessage = Utils.toUTF8(decrypted)
console.log('Decrypted message:', decryptedMessage)
return { original: secretMessage, decrypted: decryptedMessage }
}
demonstrateEncryption().catch(console.error)
import { ProtoWallet, Utils } from '@bsv/sdk'
class SecureMessaging {
private aliceWallet: ProtoWallet
private bobWallet: ProtoWallet
private aliceIdentity: string
private bobIdentity: string
constructor() {
this.aliceWallet = new ProtoWallet(PrivateKey.fromRandom())
this.bobWallet = new ProtoWallet(PrivateKey.fromRandom())
}
async initialize() {
// Get identity keys for both parties
const aliceKey = await this.aliceWallet.getPublicKey({ identityKey: true })
const bobKey = await this.bobWallet.getPublicKey({ identityKey: true })
this.aliceIdentity = aliceKey.publicKey
this.bobIdentity = bobKey.publicKey
console.log('Alice Identity:', this.aliceIdentity.substring(0, 20) + '...')
console.log('Bob Identity:', this.bobIdentity.substring(0, 20) + '...')
}
async aliceSendsToBob(message: string): Promise<string> {
const plaintext = Utils.toArray(message, 'utf8')
const { ciphertext } = await this.aliceWallet.encrypt({
plaintext,
protocolID: [1, 'secure chat'],
keyID: 'message-key',
counterparty: this.bobIdentity
})
console.log('Alice encrypts message for Bob')
return Utils.toBase64(ciphertext)
}
async bobReceivesFromAlice(ciphertext: string): Promise<string> {
const ciphertextBytes = Utils.toArray(ciphertext, 'base64')
const { plaintext } = await this.bobWallet.decrypt({
ciphertext: ciphertextBytes,
protocolID: [1, 'secure chat'],
keyID: 'message-key',
counterparty: this.aliceIdentity
})
const message = Utils.toUTF8(plaintext)
console.log('Bob decrypts message from Alice')
return message
}
}
async function demonstrateSecureMessaging() {
const messaging = new SecureMessaging()
await messaging.initialize()
const originalMessage = 'Hello Bob, this is a secure message from Alice!'
// Alice encrypts and sends
const encryptedMessage = await messaging.aliceSendsToBob(originalMessage)
console.log('Encrypted message length:', encryptedMessage.length, 'bytes')
// Bob receives and decrypts
const decryptedMessage = await messaging.bobReceivesFromAlice(encryptedMessage)
console.log('Original:', originalMessage)
console.log('Decrypted:', decryptedMessage)
console.log('Messages match:', originalMessage === decryptedMessage)
return { originalMessage, decryptedMessage }
}
demonstrateSecureMessaging().catch(console.error)
import { ProtoWallet, Utils } from '@bsv/sdk'
async function demonstrateHMAC() {
const wallet = new ProtoWallet(PrivateKey.fromRandom())
// Data to authenticate
const data = Utils.toArray('Important data requiring integrity verification', 'utf8')
// Create HMAC
const { hmac } = await wallet.createHmac({
data,
protocolID: [1, 'data integrity'],
keyID: 'hmac-key'
})
console.log('Data:', Utils.toUTF8(data))
console.log('HMAC:', Utils.toHex(hmac))
// Verify HMAC
const { valid } = await wallet.verifyHmac({
data,
hmac,
protocolID: [1, 'data integrity'],
keyID: 'hmac-key'
})
console.log('HMAC valid:', valid)
// Test with tampered data
const tamperedData = Utils.toArray('Tampered data requiring integrity verification', 'utf8')
const { valid: tamperedValid } = await wallet.verifyHmac({
data: tamperedData,
hmac,
protocolID: [1, 'data integrity'],
keyID: 'hmac-key'
})
console.log('Tampered data HMAC valid:', tamperedValid)
return { valid, tamperedValid }
}
demonstrateHMAC().catch(console.error)
import { ProtoWallet, PrivateKey, Utils } from '@bsv/sdk'
export class DevelopmentWallet {
private wallet: ProtoWallet
private identityKey: string | null = null
constructor(privateKey?: PrivateKey) {
this.wallet = new ProtoWallet(privateKey)
}
async getIdentity(): Promise<string> {
if (!this.identityKey) {
const { publicKey } = await this.wallet.getPublicKey({ identityKey: true })
this.identityKey = publicKey
}
return this.identityKey
}
async signData(data: string, protocolId: string, keyId: string): Promise<{
signature: string
publicKey: string
data: string
}> {
const dataBytes = Utils.toArray(data, 'utf8')
const { signature } = await this.wallet.createSignature({
data: dataBytes,
protocolID: [1, protocolId],
keyID: keyId,
counterparty: 'self'
})
const { publicKey } = await this.wallet.getPublicKey({
protocolID: [1, protocolId],
keyID: keyId
})
return {
signature: Utils.toBase64(signature),
publicKey,
data
}
}
async verifyData(
data: string,
signature: string,
protocolId: string,
keyId: string
): Promise<boolean> {
const dataBytes = Utils.toArray(data, 'utf8')
const signatureBytes = Utils.toArray(signature, 'base64')
const { valid } = await this.wallet.verifySignature({
data: dataBytes,
signature: signatureBytes,
protocolID: [1, protocolId],
keyID: keyId,
counterparty: 'self'
})
return valid
}
async encryptForSelf(data: string, protocolId: string, keyId: string): Promise<string> {
const plaintext = Utils.toArray(data, 'utf8')
const { ciphertext } = await this.wallet.encrypt({
plaintext,
protocolID: [1, protocolId],
keyID: keyId
})
return Utils.toBase64(ciphertext)
}
async decryptFromSelf(
encryptedData: string,
protocolId: string,
keyId: string
): Promise<string> {
const ciphertext = Utils.toArray(encryptedData, 'base64')
const { plaintext } = await this.wallet.decrypt({
ciphertext,
protocolID: [1, protocolId],
keyID: keyId
})
return Utils.toUTF8(plaintext)
}
async createDataIntegrityTag(data: string, protocolId: string, keyId: string): Promise<string> {
const dataBytes = Utils.toArray(data, 'utf8')
const { hmac } = await this.wallet.createHmac({
data: dataBytes,
protocolID: [1, protocolId],
keyID: keyId
})
return Utils.toHex(hmac)
}
async verifyDataIntegrity(
data: string,
integrityTag: string,
protocolId: string,
keyId: string
): Promise<boolean> {
const dataBytes = Utils.toArray(data, 'utf8')
const hmac = Utils.toArray(integrityTag, 'hex')
const { valid } = await this.wallet.verifyHmac({
data: dataBytes,
hmac,
protocolID: [1, protocolId],
keyID: keyId
})
return valid
}
}
async function demonstrateDevelopmentToolkit() {
const devWallet = new DevelopmentWallet()
console.log('=== Development Wallet Toolkit Demo ===')
// Get identity
const identity = await devWallet.getIdentity()
console.log('Wallet Identity:', identity.substring(0, 20) + '...')
// Sign and verify data
const testData = 'Development test data for signing'
const signatureResult = await devWallet.signData(testData, 'dev-tools', 'test-key')
console.log('Data signed successfully')
const isValid = await devWallet.verifyData(
testData,
signatureResult.signature,
'dev-tools',
'test-key'
)
console.log('Signature verification:', isValid ? 'PASSED' : 'FAILED')
// Encrypt and decrypt data
const secretData = 'Confidential development information'
const encrypted = await devWallet.encryptForSelf(secretData, 'dev-storage', 'secret-key')
console.log('Data encrypted successfully')
const decrypted = await devWallet.decryptFromSelf(encrypted, 'dev-storage', 'secret-key')
console.log('Decryption result:', secretData === decrypted ? 'PASSED' : 'FAILED')
// Create and verify integrity tag
const importantData = 'Critical development configuration'
const integrityTag = await devWallet.createDataIntegrityTag(
importantData,
'dev-integrity',
'config-key'
)
console.log('Integrity tag created')
const integrityValid = await devWallet.verifyDataIntegrity(
importantData,
integrityTag,
'dev-integrity',
'config-key'
)
console.log('Integrity verification:', integrityValid ? 'PASSED' : 'FAILED')
return {
identity,
signatureValid: isValid,
decryptionValid: secretData === decrypted,
integrityValid
}
}
demonstrateDevelopmentToolkit().catch(console.error)
```typescript import { ProtoWallet, PrivateKey, Utils } from ‘@bsv/sdk’
export class ProtoWalletTestUtils { static createTestWallet(seed?: string): ProtoWallet { if (seed) { // Create deterministic wallet for testing const hash = Utils.toArray(seed, ‘utf8’) const privateKey = PrivateKey.fromString(Utils.toHex(hash).padEnd(64, ‘0’)) return new ProtoWallet(privateKey) } return new ProtoWallet(PrivateKey.fromRandom()) }
static async createTestIdentities(count: number): Promise<{ wallets: ProtoWallet[] identities: string[] }> { const wallets: ProtoWallet[] = [] const identities: string[] = []
for (let i = 0; i < count; i++) {
const wallet = this.createTestWallet(`test-identity-${i}`)
const { publicKey } = await wallet.getPublicKey({ identityKey: true })
wallets.push(wallet)
identities.push(publicKey)
}
return { wallets, identities } }
static async testCryptographicRoundTrip( wallet: ProtoWallet, data: string, protocolId: string, keyId: string ): Promise<{ signatureValid: boolean encryptionValid: boolean hmacValid: boolean }> { const dataBytes = Utils.toArray(data, ‘utf8’)
// Test signature round trip
const { signature } = await wallet.createSignature({
data: dataBytes,
protocolID: [1, protocolId],
keyID: keyId,
counterparty: 'self'
})
const { valid: signatureValid } = await wallet.verifySignature({
data: dataBytes,
signature,
protocolID: [1, protocolId],
keyID: keyId,
counterparty: 'self'
})
// Test encryption round trip
const { ciphertext } = await wallet.encrypt({
plaintext: dataBytes,
protocolID: [1, protocolId],
keyID: keyId
})
const { plaintext } = await wallet.decrypt({
ciphertext,
protocolID: [1, protocolId],
keyID: keyId
})
const encryptionValid = Utils.toUTF8(plaintext) === data
// Test HMAC round trip
const { hmac } = await wallet.createHmac({
data: dataBytes,
protocolID: [1, protocolId],
keyID: keyId
})
const { valid: hmacValid } = await wallet.verifyHmac({
data: dataBytes,
hmac,
protocolID: [1, protocolId],
keyID: keyId
})
return { signatureValid, encryptionValid, hmacValid } } }
async function runTestSuite() { console.log(‘=== ProtoWallet Test Suite ===’)
// Test deterministic wallet creation const wallet1 = ProtoWalletTestUtils.createTestWallet(‘test-seed-123’) const wallet2 = ProtoWalletTestUtils.createTestWallet(‘test-seed-123’)
const identity1 = await wallet1.getPublicKey({ identityKey: true }) const identity2 = await wallet2.getPublicKey({ identityKey: true })
console.log(‘Deterministic wallet test:’, identity1.publicKey === identity2.publicKey ? ‘PASSED’ : ‘FAILED’)
// Test multiple identities const { wallets, identities } = await ProtoWalletTestUtils.createTestIdentities(3) console.log(‘Created test identities:’, identities.length)
// Test cryptographic operations const testData = ‘Test data for cryptographic operations’ const results = await ProtoWalletTestUtils.testCryptographicRoundTrip( wallets[0], testData, ‘test-protocol’, ‘test-key’ )
console.log(‘Cryptographic tests:’) console.log(‘ Signature:’, results.signatureValid ? ‘PASSED’ : ‘FAILED’) console.log(‘ Encryption:’, results.encryptionValid ? ‘PASSED’ : ‘FAILED’) console.log(‘ HMAC:’, results.hmacValid ? ‘PASSED’ : ‘FAILED’)
return results }
runTestSuite().catch(console.error)