├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── wallet ├── substrates │ ├── testdata │ │ ├── getHeight-simple-args.json │ │ ├── isAuthenticated-simple-args.json │ │ ├── waitForAuthentication-simple-args.json │ │ ├── abortAction-simple-result.json │ │ ├── verifyHmac-simple-result.json │ │ ├── getNetwork-simple-result.json │ │ ├── verifySignature-simple-result.json │ │ ├── getHeight-simple-result.json │ │ ├── internalizeAction-simple-result.json │ │ ├── relinquishOutput-simple-result.json │ │ ├── abortAction-simple-args.json │ │ ├── getVersion-simple-result.json │ │ ├── isAuthenticated-simple-result.json │ │ ├── relinquishCertificate-simple-result.json │ │ ├── waitForAuthentication-simple-result.json │ │ ├── getHeaderForHeight-simple-args.json │ │ ├── decrypt-simple-result.json │ │ ├── proveCertificate-simple-result.json │ │ ├── getPublicKey-simple-result.json │ │ ├── encrypt-simple-result.json │ │ ├── listActions-simple-args.json │ │ ├── relinquishOutput-simple-args.json │ │ ├── discoverByIdentityKey-simple-args.json │ │ ├── discoverByAttributes-simple-args.json │ │ ├── signAction-simple-args.json │ │ ├── listOutputs-simple-args.json │ │ ├── getHeaderForHeight-simple-result.json │ │ ├── createHmac-simple-args.json │ │ ├── encrypt-simple-args.json │ │ ├── relinquishCertificate-simple-args.json │ │ ├── revealCounterpartyKeyLinkage-simple-args.json │ │ ├── createSignature-simple-args.json │ │ ├── getPublicKey-simple-args.json │ │ ├── decrypt-simple-args.json │ │ ├── revealSpecificKeyLinkage-simple-args.json │ │ ├── createHmac-simple-result.json │ │ ├── acquireCertificate-issuance-args.json │ │ ├── createSignature-simple-result.json │ │ ├── listCertificates-simple-args.json │ │ ├── createAction-1-out-args.json │ │ ├── revealCounterpartyKeyLinkage-simple-result.json │ │ ├── listOutputs-simple-result.json │ │ ├── revealSpecificKeyLinkage-simple-result.json │ │ ├── verifyHmac-simple-args.json │ │ ├── listActions-simple-result.json │ │ ├── verifySignature-simple-args.json │ │ ├── internalizeAction-simple-args.json │ │ ├── acquireCertificate-simple-result.json │ │ ├── listCertificates-simple-result.json │ │ ├── acquireCertificate-simple-args.json │ │ ├── proveCertificate-simple-args.json │ │ ├── listCertificates-full-result.json │ │ ├── discoverByAttributes-simple-result.json │ │ └── discoverByIdentityKey-simple-result.json │ ├── wallet_wire.go │ └── wallet_wire_calls.go ├── serializer │ ├── get_version.go │ ├── get_height.go │ ├── get_header.go │ ├── get_network_test.go │ ├── abort_action.go │ ├── authenticated.go │ ├── get_network.go │ ├── certificate_test.go │ ├── get_version_test.go │ ├── sign_action_result.go │ ├── discover_by_attributes_test.go │ ├── discover_by_identity_key.go │ ├── relinquish_output.go │ ├── discover_by_identity_key_test.go │ ├── get_height_test.go │ ├── discover_certificates_result.go │ ├── discover_by_attributes.go │ ├── relinquish_certificate_test.go │ ├── relinquish_output_test.go │ ├── relinquish_certificate.go │ ├── authenticated_test.go │ ├── get_header_test.go │ ├── decrypt.go │ ├── encrypt.go │ └── create_hmac_test.go └── error.go ├── docs ├── examples │ ├── create_tx_with_inscription │ │ ├── 1SatLogoLight.png │ │ └── create_tx_with_inscription.go │ ├── hd_key_from_xpub │ │ ├── hd_key_from_xpub.go │ │ └── README.md │ ├── address_from_wif │ │ └── address_from_wif.go │ ├── aes │ │ └── aes.go │ ├── generate_hd_key │ │ └── generate_hd_key.go │ ├── derive_child │ │ └── derive_child.go │ ├── broadcaster │ │ ├── broadcaster_gp │ │ │ └── broadcaster_gp.go │ │ ├── broadcaster_woc │ │ │ └── broadcaster_woc.go │ │ └── broadcaster.md │ ├── keyshares_pk_to_backup │ │ └── to_backup.go │ ├── ecies_electrum_binary │ │ └── ecies_electrum_binary.go │ ├── ecies_single │ │ └── ecies_single.go │ ├── keyshares_pk_from_backup │ │ └── from_backup.go │ ├── encrypted_message │ │ └── message.go │ ├── GO_BT.md │ ├── create_tx_with_op_return │ │ └── create_tx_with_op_return.go │ ├── identity_client │ │ └── README.md │ ├── ecies_shared │ │ └── ecies_shared.go │ ├── set_source_tx_output │ │ ├── set_source_tx_output.go │ │ └── README.md │ ├── fee_modeling │ │ └── fee_modeling.go │ ├── verify_beef │ │ └── verify_beef.go │ ├── verify_transaction │ │ └── verify_transaction.go │ └── registry_register │ │ └── registry_register.go ├── README.md ├── low-level │ ├── README.md │ └── type_42 │ │ └── type_42.go └── concepts │ └── README.md ├── primitives ├── ec │ ├── point.go │ ├── curve.go │ ├── wif_test.go │ ├── testdata │ │ ├── SymmetricKey.vectors.json │ │ ├── BRC42.private.vectors.json │ │ └── BRC42.public.vectors.json │ └── precompute.go ├── hash │ └── hash.go ├── ecdsa │ └── testdata │ │ ├── BRC42.private.vectors.json │ │ └── BRC42.public.vectors.json ├── aesgcm │ └── ghash.go ├── drbg │ └── drbg_test.go └── aescbc │ └── cbc.go ├── transaction ├── fee_model │ ├── errors.go │ ├── sats_per_kb.go │ └── sats_per_kb_test.go ├── script_template.go ├── chaintracker │ └── chaintracker.go ├── beefTx.go ├── broadcaster.go ├── consts.go ├── merkletreeparent_test.go ├── utxo.go ├── output_test.go ├── fees.go ├── merkletreeparent.go ├── sighash │ └── flag.go └── broadcaster │ └── taal.go ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── discussion.md │ └── bug_report.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── golangci-lint.yaml │ ├── codecov.yaml │ └── sonar.yaml ├── .golangci.yml ├── util ├── http-client.go ├── bytemanipulation.go ├── bytestring.go ├── test_cert_util │ └── test_certificate_utils.go └── big.go ├── script ├── interpreter │ ├── data │ │ └── LICENSE │ ├── debug │ │ ├── options.go │ │ └── example_test.go │ ├── consensus.go │ ├── README.md │ ├── engine.go │ ├── doc.go │ └── config.go ├── inscriptions.go ├── errors.go ├── addressvalidation_test.go └── addressvalidation.go ├── .goreleaser.yml ├── go.mod ├── auth ├── utils │ ├── base64.go │ ├── create_nonce.go │ └── verify_nonce.go ├── transports │ ├── errors.go │ └── interface.go └── brc104 │ └── auth_http_headers.go ├── internal └── logging │ └── test_logger.go ├── sonar-project.properties ├── spv └── scripts_only.go ├── .gitignore ├── chainhash └── hashfuncs.go ├── overlay ├── lookup │ ├── types.go │ └── facilitator.go └── topic │ └── facilitator.go ├── go.sum └── compat ├── bsm └── sign.go └── base58 ├── alphabet.go └── base58.go /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["SonarSource.sonarlint-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/getHeight-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": {}, 3 | "wire": "1900" 4 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/isAuthenticated-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": {}, 3 | "wire": "1700" 4 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/waitForAuthentication-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": {}, 3 | "wire": "1800" 4 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/abortAction-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "aborted": true 4 | }, 5 | "wire": "00" 6 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/verifyHmac-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "valid": true 4 | }, 5 | "wire": "00" 6 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/getNetwork-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "network": "mainnet" 4 | }, 5 | "wire": "0000" 6 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/verifySignature-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "valid": true 4 | }, 5 | "wire": "00" 6 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/getHeight-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "height": 850000 4 | }, 5 | "wire": "00fe50f80c00" 6 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/internalizeAction-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "accepted": true 4 | }, 5 | "wire": "00" 6 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/relinquishOutput-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "relinquished": true 4 | }, 5 | "wire": "00" 6 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/abortAction-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "reference": "dGVzdA==" 4 | }, 5 | "wire": "030074657374" 6 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/getVersion-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "version": "1.0.0" 4 | }, 5 | "wire": "00312e302e30" 6 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/isAuthenticated-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "authenticated": true 4 | }, 5 | "wire": "0001" 6 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/relinquishCertificate-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "relinquished": true 4 | }, 5 | "wire": "00" 6 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/waitForAuthentication-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "authenticated": true 4 | }, 5 | "wire": "00" 6 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/getHeaderForHeight-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "height": 850000 4 | }, 5 | "wire": "1a00fe50f80c00" 6 | } -------------------------------------------------------------------------------- /docs/examples/create_tx_with_inscription/1SatLogoLight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsv-blockchain/go-sdk/HEAD/docs/examples/create_tx_with_inscription/1SatLogoLight.png -------------------------------------------------------------------------------- /primitives/ec/point.go: -------------------------------------------------------------------------------- 1 | package primitives 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | type Point struct { 8 | Curve *Curve 9 | X, Y *big.Int 10 | } 11 | -------------------------------------------------------------------------------- /transaction/fee_model/errors.go: -------------------------------------------------------------------------------- 1 | package feemodel 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrNoUnlockingScript = errors.New("inputs must have an unlocking script or an unlocker") 7 | ) 8 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/decrypt-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "plaintext": [ 4 | 1, 5 | 2, 6 | 3, 7 | 4 8 | ] 9 | }, 10 | "wire": "0001020304" 11 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/proveCertificate-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "keyringForVerifier": { 4 | "name": "bmFtZS1rZXk=" 5 | } 6 | }, 7 | "wire": "0001046e616d65086e616d652d6b6579" 8 | } -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Use CODEOWNERS to manage reviewers for Dependabot PRs per GitHub change: 2 | # https://github.blog/changelog/2025-04-29-dependabot-reviewers-configuration-option-being-replaced-by-code-owners/ 3 | 4 | * @shruggr @rohenaz 5 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/getPublicKey-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "publicKey": "025ad43a22ac38d0bc1f8bacaabb323b5d634703b7a774c4268f6a09e4ddf79097" 4 | }, 5 | "wire": "00025ad43a22ac38d0bc1f8bacaabb323b5d634703b7a774c4268f6a09e4ddf79097" 6 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/encrypt-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "ciphertext": [ 4 | 1, 5 | 2, 6 | 3, 7 | 4, 8 | 5, 9 | 6, 10 | 7, 11 | 8 12 | ] 13 | }, 14 | "wire": "000102030405060708" 15 | } -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | linters: 4 | exclusions: 5 | rules: 6 | - linters: 7 | - staticcheck 8 | text: "SA1019:" 9 | - linters: 10 | - errcheck 11 | text: "Error return value of `.*\\.Close` is not checked" -------------------------------------------------------------------------------- /wallet/substrates/testdata/listActions-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "labels": [ 4 | "test-label" 5 | ], 6 | "includeOutputs": true, 7 | "limit": 10 8 | }, 9 | "wire": "0400010a746573742d6c6162656cffffffffff01ff0affffffffffffffffffff" 10 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/relinquishOutput-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "basket": "test-basket", 4 | "output": "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890.2" 5 | }, 6 | "wire": "07000b746573742d6261736b6574abcdef1234567890abcdef1234567890abcdef1234567890abcdef123456789002" 7 | } -------------------------------------------------------------------------------- /transaction/script_template.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "github.com/bsv-blockchain/go-sdk/script" 5 | ) 6 | 7 | type UnlockingScriptTemplate interface { 8 | Sign(tx *Transaction, inputIndex uint32) (*script.Script, error) 9 | EstimateLength(tx *Transaction, inputIndex uint32) uint32 10 | } 11 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/discoverByIdentityKey-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "identityKey": "0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1", 4 | "limit": 10, 5 | "offset": 0, 6 | "seekPermission": true 7 | }, 8 | "wire": "15000294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a10a0001" 9 | } -------------------------------------------------------------------------------- /wallet/substrates/wallet_wire.go: -------------------------------------------------------------------------------- 1 | package substrates 2 | 3 | import "context" 4 | 5 | // WalletWire is an abstraction over a raw transport medium 6 | // where binary data can be sent to and subsequently received from a wallet. 7 | type WalletWire interface { 8 | TransmitToWallet(ctx context.Context, message []byte) ([]byte, error) 9 | } 10 | -------------------------------------------------------------------------------- /transaction/chaintracker/chaintracker.go: -------------------------------------------------------------------------------- 1 | package chaintracker 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/bsv-blockchain/go-sdk/chainhash" 7 | ) 8 | 9 | type ChainTracker interface { 10 | IsValidRootForHeight(ctx context.Context, root *chainhash.Hash, height uint32) (bool, error) 11 | CurrentHeight(ctx context.Context) (uint32, error) 12 | } 13 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/discoverByAttributes-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "attributes": { 4 | "email": "alice@example.com", 5 | "role": "admin" 6 | }, 7 | "limit": 5, 8 | "offset": 0, 9 | "seekPermission": false 10 | }, 11 | "wire": "16000205656d61696c11616c696365406578616d706c652e636f6d04726f6c650561646d696e050000" 12 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/signAction-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "reference": "dGVzdA==", 4 | "spends": { 5 | "0": { 6 | "unlockingScript": "76a91489abcdefabbaabbaabbaabbaabbaabbaabbaabba88ac" 7 | } 8 | } 9 | }, 10 | "wire": "020001001976a91489abcdefabbaabbaabbaabbaabbaabbaabbaabba88acffffffffffffffffff047465737400" 11 | } -------------------------------------------------------------------------------- /util/http-client.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | type HTTPClient interface { 9 | Do(req *http.Request) (*http.Response, error) 10 | } 11 | 12 | type HTTPError struct { 13 | StatusCode int 14 | Err error 15 | } 16 | 17 | func (e *HTTPError) Error() string { 18 | return fmt.Sprintf("%d-%s", e.StatusCode, e.Err.Error()) 19 | } 20 | -------------------------------------------------------------------------------- /script/interpreter/data/LICENSE: -------------------------------------------------------------------------------- 1 | The json files in this directory come from the bitcoind project 2 | (https://github.com/bitcoin/bitcoin) and is released under the following 3 | license: 4 | 5 | Copyright (c) 2012-2014 The Bitcoin Core developers 6 | Distributed under the MIT/X11 software license, see the accompanying 7 | file COPYING or http://www.opensource.org/licenses/mit-license.php. 8 | 9 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | before: 4 | hooks: 5 | - go mod tidy 6 | - golangci-lint run 7 | 8 | snapshot: 9 | name_template: "{{ .Tag }}" 10 | 11 | changelog: 12 | sort: asc 13 | filters: 14 | exclude: 15 | - '^.github:' 16 | - '^test:' 17 | 18 | builds: 19 | - skip: true 20 | 21 | release: 22 | # prerelease: true 23 | name_template: "Release v{{.Version}}" -------------------------------------------------------------------------------- /wallet/substrates/testdata/listOutputs-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "basket": "test-basket", 4 | "tags": [ 5 | "tag1", 6 | "tag2" 7 | ], 8 | "tagQueryMode": "any", 9 | "include": "locking scripts", 10 | "includeTags": true, 11 | "limit": 10 12 | }, 13 | "wire": "06000b746573742d6261736b657402047461673104746167320201ff01ff0affffffffffffffffffff" 14 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bsv-blockchain/go-sdk 2 | 3 | go 1.24.3 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.1 7 | github.com/stretchr/testify v1.11.1 8 | golang.org/x/crypto v0.45.0 9 | golang.org/x/sync v0.18.0 10 | ) 11 | 12 | require golang.org/x/net v0.47.0 13 | 14 | require ( 15 | github.com/pkg/errors v0.9.1 16 | github.com/pmezard/go-difflib v1.0.0 // indirect 17 | gopkg.in/yaml.v3 v3.0.1 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /script/interpreter/debug/options.go: -------------------------------------------------------------------------------- 1 | package debug 2 | 3 | // DebuggerOptionFunc for setting debugger options. 4 | type DebuggerOptionFunc func(o *debugOpts) 5 | 6 | // WithRewind configure the debugger to enable rewind functionality. When 7 | // enabled, the debugger will save each stack frame from BeforeStep to memory. 8 | func WithRewind() DebuggerOptionFunc { 9 | return func(o *debugOpts) { 10 | o.rewind = true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /wallet/serializer/get_version.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "github.com/bsv-blockchain/go-sdk/wallet" 5 | ) 6 | 7 | func SerializeGetVersionResult(result *wallet.GetVersionResult) ([]byte, error) { 8 | return []byte(result.Version), nil 9 | } 10 | 11 | func DeserializeGetVersionResult(data []byte) (*wallet.GetVersionResult, error) { 12 | return &wallet.GetVersionResult{ 13 | Version: string(data), 14 | }, nil 15 | } 16 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/getHeaderForHeight-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "header": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c" 4 | }, 5 | "wire": "000100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c" 6 | } -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Go Library API Documentation 2 | 3 | The documentation is split into various pages, each covering a set of related functionality. The pages are as follows: 4 | 5 | - [Concepts](./concepts/) — Covers high-level concepts and architecture of the Go SDK. 6 | - [Examples](./examples/) — Provides practical examples of how to use the Go SDK. 7 | - [Low-Level](./low-level/) — Details low-level operations and functionalities within the SDK. 8 | -------------------------------------------------------------------------------- /auth/utils/base64.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/base64" 6 | 7 | "github.com/bsv-blockchain/go-sdk/wallet" 8 | ) 9 | 10 | // RandomBase64 generates a random byte sequence of specified length and returns it as base64 encoded string 11 | func RandomBase64(length int) wallet.StringBase64 { 12 | randomBytes := make([]byte, length) 13 | _, _ = rand.Read(randomBytes) 14 | return wallet.StringBase64(base64.StdEncoding.EncodeToString(randomBytes)) 15 | } 16 | -------------------------------------------------------------------------------- /script/inscriptions.go: -------------------------------------------------------------------------------- 1 | package script 2 | 3 | // InscriptionArgs contains the Ordinal inscription data. 4 | type InscriptionArgs struct { 5 | LockingScript *Script 6 | Data []byte 7 | ContentType string 8 | EnrichedArgs *EnrichedInscriptionArgs 9 | } 10 | 11 | // EnrichedInscriptionArgs contains data needed for enriched inscription 12 | // functionality found here: https://docs.1satordinals.com/op_return. 13 | type EnrichedInscriptionArgs struct { 14 | OpReturnData [][]byte 15 | } 16 | -------------------------------------------------------------------------------- /auth/transports/errors.go: -------------------------------------------------------------------------------- 1 | // Package transports provides implementations of the auth.Transport interface 2 | package transports 3 | 4 | import ( 5 | "errors" 6 | ) 7 | 8 | // Common errors for all transports 9 | var ( 10 | // ErrNoHandlerRegistered is returned when trying to send a message without registering an OnData handler 11 | ErrNoHandlerRegistered = errors.New("no OnData handler registered") 12 | ErrHTTPServerFailedToAuthenticate = errors.New("HTTP server failed to authenticate") 13 | ) 14 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/createHmac-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "protocolID": [ 4 | 1, 5 | "test-protocol" 6 | ], 7 | "keyID": "test-key", 8 | "counterparty": "self", 9 | "privileged": true, 10 | "privilegedReason": "test reason", 11 | "seekPermission": true, 12 | "data": [ 13 | 10, 14 | 20, 15 | 30, 16 | 40 17 | ] 18 | }, 19 | "wire": "0d00010d746573742d70726f746f636f6c08746573742d6b65790b010b7465737420726561736f6e040a141e2801" 20 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/encrypt-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "protocolID": [ 4 | 1, 5 | "test-protocol" 6 | ], 7 | "keyID": "test-key", 8 | "counterparty": "self", 9 | "privileged": true, 10 | "privilegedReason": "test reason", 11 | "seekPermission": true, 12 | "plaintext": [ 13 | 1, 14 | 2, 15 | 3, 16 | 4 17 | ] 18 | }, 19 | "wire": "0b00010d746573742d70726f746f636f6c08746573742d6b65790b010b7465737420726561736f6e040102030401" 20 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/relinquishCertificate-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "type": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0ZXN0LXR5cGU=", 4 | "serialNumber": "AAAAAAAAAAAAAAAAAAB0ZXN0LXNlcmlhbC1udW1iZXI=", 5 | "certifier": "0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1" 6 | }, 7 | "wire": "14000000000000000000000000000000000000000000000000746573742d747970650000000000000000000000000000746573742d73657269616c2d6e756d6265720294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1" 8 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/revealCounterpartyKeyLinkage-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "counterparty": "0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1", 4 | "verifier": "03b106dae20ae8fca0f4e8983d974c4b583054573eecdcdcfad261c035415ce1ee", 5 | "privileged": true, 6 | "privilegedReason": "test-reason" 7 | }, 8 | "wire": "0900010b746573742d726561736f6e0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a103b106dae20ae8fca0f4e8983d974c4b583054573eecdcdcfad261c035415ce1ee" 9 | } -------------------------------------------------------------------------------- /internal/logging/test_logger.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "log/slog" 5 | "testing" 6 | ) 7 | 8 | type testLogger struct { 9 | t testing.TB 10 | } 11 | 12 | func (w testLogger) Write(p []byte) (n int, err error) { 13 | w.t.Helper() 14 | w.t.Log(string(p)) 15 | return len(p), nil 16 | } 17 | 18 | func NewTestLogger(t testing.TB) *slog.Logger { 19 | handler := slog.NewTextHandler(&testLogger{t: t}, &slog.HandlerOptions{ 20 | Level: slog.LevelDebug, 21 | }) 22 | 23 | return slog.New(handler) 24 | } 25 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/createSignature-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "protocolID": [ 4 | 1, 5 | "test-protocol" 6 | ], 7 | "keyID": "test-key", 8 | "counterparty": "self", 9 | "privileged": true, 10 | "privilegedReason": "test reason", 11 | "seekPermission": true, 12 | "data": [ 13 | 11, 14 | 22, 15 | 33, 16 | 44 17 | ] 18 | }, 19 | "wire": "0f00010d746573742d70726f746f636f6c08746573742d6b65790b010b7465737420726561736f6e01040b16212c01" 20 | } -------------------------------------------------------------------------------- /script/interpreter/consensus.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package interpreter 6 | 7 | const ( 8 | // LockTimeThreshold is the number below which a lock time is 9 | // interpreted to be a block number. Since an average of one block 10 | // is generated per 10 minutes, this allows blocks for about 9,512 11 | // years. 12 | LockTimeThreshold = 5e8 // Tue Nov 5 00:53:20 1985 UTC 13 | ) 14 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/getPublicKey-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "protocolID": [ 4 | 2, 5 | "tests" 6 | ], 7 | "keyID": "test-key-id", 8 | "counterparty": "0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1", 9 | "privileged": true, 10 | "privilegedReason": "privileged reason", 11 | "seekPermission": true 12 | }, 13 | "wire": "080000020574657374730b746573742d6b65792d69640294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1011170726976696c6567656420726561736f6eff01" 14 | } -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=bsv-blockchain_go-sdk 2 | sonar.organization=bsv-blockchain 3 | sonar.projectName=Go SDK 4 | sonar.projectDescription=BSV BLOCKCHAIN | Software Development Kit for Go 5 | sonar.projectVersion=1.0.0 6 | sonar.sources=. 7 | sonar.sourceEncoding=UTF-8 8 | sonar.go.coverage.reportPaths=coverage.out 9 | sonar.go.golangci-lint.reportPaths=golangci-lint-report.xml 10 | 11 | sonar.exclusions=**test**,**/docs/**,**vendor** 12 | sonar.coverage.exclusions=docs/** 13 | sonar.tests=. 14 | sonar.test.inclusions=**/*_test.go 15 | sonar.python.version=3.12 16 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/decrypt-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "protocolID": [ 4 | 1, 5 | "test-protocol" 6 | ], 7 | "keyID": "test-key", 8 | "counterparty": "self", 9 | "privileged": true, 10 | "privilegedReason": "test reason", 11 | "seekPermission": true, 12 | "ciphertext": [ 13 | 1, 14 | 2, 15 | 3, 16 | 4, 17 | 5, 18 | 6, 19 | 7, 20 | 8 21 | ] 22 | }, 23 | "wire": "0c00010d746573742d70726f746f636f6c08746573742d6b65790b010b7465737420726561736f6e08010203040506070801" 24 | } -------------------------------------------------------------------------------- /transaction/beefTx.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "github.com/bsv-blockchain/go-sdk/chainhash" 5 | ) 6 | 7 | // DataFormat represents the format of the data in a BeefTx. 8 | type DataFormat int 9 | 10 | const ( 11 | RawTx DataFormat = iota 12 | RawTxAndBumpIndex 13 | TxIDOnly 14 | ) 15 | 16 | // BeefTx represents a Transaction or Txid within a Beef with or without reference to a BUMP. 17 | type BeefTx struct { 18 | DataFormat DataFormat 19 | KnownTxID *chainhash.Hash 20 | Transaction *Transaction 21 | BumpIndex int 22 | InputTxids []*chainhash.Hash 23 | } 24 | -------------------------------------------------------------------------------- /docs/low-level/README.md: -------------------------------------------------------------------------------- 1 | # Low-Level Constructs 2 | 3 | These documents cover the lower level functionalities of the BSV library. These are the building blocks for higher level functionality and are not intended for general use. 4 | 5 | ## Index 6 | 7 | - Using Scripts 8 | - Code: `docs/low-level/using_scripts/using_scripts.go` 9 | - Focus: Working directly with scripts (ASM/hex), constructing and parsing locking/unlocking scripts. 10 | 11 | - Type 42 (BRC-42 Primitives) 12 | - Code: `docs/low-level/type_42/type_42.go` 13 | - Focus: Low-level usage of BRC-42 encodings and related primitives. 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/discussion.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Discussion 3 | about: Propose a discussion or seek clarification about a feature or topic. 4 | title: "[DISCUSSION]" 5 | labels: discussion 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Summary 11 | 12 | Briefly describe the topic you'd like to discuss. 13 | 14 | ## Motivation 15 | 16 | Why do you believe this to be important? 17 | 18 | ## Description 19 | 20 | Provide a detailed description or elaborate on your topic. 21 | 22 | ## Additional References 23 | 24 | Provide any additional articles, links, or context that would help facilitate the discussion. 25 | -------------------------------------------------------------------------------- /docs/examples/hd_key_from_xpub/hd_key_from_xpub.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | bip32 "github.com/bsv-blockchain/go-sdk/compat/bip32" 7 | ) 8 | 9 | func main() { 10 | 11 | // Start with an existing xPub 12 | xPub := "xpub661MyMwAqRbcH3WGvLjupmr43L1GVH3MP2WQWvdreDraBeFJy64Xxv4LLX9ZVWWz3ZjZkMuZtSsc9qH9JZR74bR4PWkmtEvP423r6DJR8kA" 13 | 14 | // Convert to a HD key 15 | key, err := bip32.GetHDKeyFromExtendedPublicKey(xPub) 16 | if err != nil { 17 | log.Fatalf("error occurred: %s", err.Error()) 18 | } 19 | 20 | log.Printf("converted key: %s private: %v", key.String(), key.IsPrivate()) 21 | } 22 | -------------------------------------------------------------------------------- /spv/scripts_only.go: -------------------------------------------------------------------------------- 1 | package spv 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/bsv-blockchain/go-sdk/chainhash" 7 | ) 8 | 9 | type GullibleHeadersClient struct{} 10 | 11 | func (g *GullibleHeadersClient) IsValidRootForHeight(ctx context.Context, merkleRoot *chainhash.Hash, height uint32) (bool, error) { 12 | // DO NOT USE IN A REAL PROJECT due to security risks of accepting any merkle root as valid without verification 13 | return true, nil 14 | } 15 | 16 | func (g *GullibleHeadersClient) CurrentHeight(ctx context.Context) (uint32, error) { 17 | return 800000, nil // Return a dummy height for testing 18 | } 19 | -------------------------------------------------------------------------------- /docs/examples/address_from_wif/address_from_wif.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | ec "github.com/bsv-blockchain/go-sdk/primitives/ec" 7 | script "github.com/bsv-blockchain/go-sdk/script" 8 | ) 9 | 10 | func main() { 11 | 12 | priv, _ := ec.PrivateKeyFromWif("Kxfd8ABTYZHBH3y1jToJ2AUJTMVbsNaqQsrkpo9gnnc1JXfBH8mn") 13 | 14 | // Print the private key 15 | log.Printf("Private key: %x\n", priv.Serialize()) 16 | address, _ := script.NewAddressFromPublicKey(priv.PubKey(), true) 17 | 18 | // Print the address, and the pubkey hash 19 | println(address.AddressString, address.PublicKeyHash) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /wallet/error.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import "fmt" 4 | 5 | // Error represents a wallet-specific error with a code, message and stack trace. 6 | // It implements the standard error interface and provides structured error information 7 | // for wallet operations that can fail. 8 | type Error struct { 9 | Code byte 10 | Message string 11 | Stack string 12 | } 13 | 14 | // Error returns a formatted string representation of the wallet error. 15 | // It implements the standard error interface by combining the error code and message. 16 | func (e *Error) Error() string { 17 | return fmt.Sprintf("WalletError %d: %s", e.Code, e.Message) 18 | } 19 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/revealSpecificKeyLinkage-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "counterparty": "0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1", 4 | "verifier": "03b106dae20ae8fca0f4e8983d974c4b583054573eecdcdcfad261c035415ce1ee", 5 | "protocolID": [ 6 | 2, 7 | "tests" 8 | ], 9 | "keyID": "test-key-id", 10 | "privileged": true, 11 | "privilegedReason": "test-reason" 12 | }, 13 | "wire": "0a00020574657374730b746573742d6b65792d69640294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1010b746573742d726561736f6e03b106dae20ae8fca0f4e8983d974c4b583054573eecdcdcfad261c035415ce1ee" 14 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch file", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "debug", 12 | "program": "${file}" 13 | }, 14 | { 15 | "name": "Launch Package", 16 | "type": "go", 17 | "request": "launch", 18 | "mode": "auto", 19 | "program": "${fileDirname}", 20 | "envFile": "${workspaceFolder}/.env", 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/createHmac-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "hmac": [ 4 | 50, 5 | 60, 6 | 70, 7 | 80, 8 | 90, 9 | 100, 10 | 110, 11 | 120, 12 | 50, 13 | 60, 14 | 70, 15 | 80, 16 | 90, 17 | 100, 18 | 110, 19 | 120, 20 | 50, 21 | 60, 22 | 70, 23 | 80, 24 | 90, 25 | 100, 26 | 110, 27 | 120, 28 | 50, 29 | 60, 30 | 70, 31 | 80, 32 | 90, 33 | 100, 34 | 110, 35 | 120 36 | ] 37 | }, 38 | "wire": "00323c46505a646e78323c46505a646e78323c46505a646e78323c46505a646e78" 39 | } -------------------------------------------------------------------------------- /docs/examples/aes/aes.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | 7 | aes "github.com/bsv-blockchain/go-sdk/primitives/aesgcm" 8 | ) 9 | 10 | // Vanilla AES encryption and decryption 11 | func main() { 12 | key, _ := hex.DecodeString("000102030405060708090a0b0c0d0e0f") 13 | 14 | // Encrypt using the public key of the given private key 15 | encryptedData, err := aes.AESEncrypt([]byte("0123456789abcdef"), key) 16 | if err != nil { 17 | fmt.Println(err) 18 | } 19 | 20 | // Decrypt using the private key 21 | decryptedData, err := aes.AESDecrypt(encryptedData, key) 22 | if err != nil { 23 | fmt.Println(err) 24 | } 25 | fmt.Printf("decryptedData: %s\n", decryptedData) 26 | } 27 | -------------------------------------------------------------------------------- /docs/examples/generate_hd_key/generate_hd_key.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha256" 5 | "log" 6 | 7 | bip32 "github.com/bsv-blockchain/go-sdk/compat/bip32" 8 | ) 9 | 10 | func main() { 11 | xPrivateKey, xPublicKey, err := bip32.GenerateHDKeyPair(bip32.SecureSeedLength) 12 | if err != nil { 13 | log.Fatalf("error occurred: %s", err.Error()) 14 | } 15 | 16 | // Success! Avoid logging sensitive key material. Use a fingerprint of the public key 17 | // for verification instead of printing the full keys. 18 | publicKeyFingerprint := sha256.Sum256([]byte(xPublicKey)) 19 | log.Printf("Generated HD key pair (xPriv length: %d, xPub fingerprint: %x)", len(xPrivateKey), publicKeyFingerprint[:8]) 20 | } 21 | -------------------------------------------------------------------------------- /wallet/serializer/get_height.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/bsv-blockchain/go-sdk/util" 7 | "github.com/bsv-blockchain/go-sdk/wallet" 8 | ) 9 | 10 | func SerializeGetHeightResult(result *wallet.GetHeightResult) ([]byte, error) { 11 | w := util.NewWriter() 12 | w.WriteVarInt(uint64(result.Height)) 13 | return w.Buf, nil 14 | } 15 | 16 | func DeserializeGetHeightResult(data []byte) (*wallet.GetHeightResult, error) { 17 | r := util.NewReaderHoldError(data) 18 | height := r.ReadVarInt32() 19 | r.CheckComplete() 20 | if r.Err != nil { 21 | return nil, fmt.Errorf("error reading height: %w", r.Err) 22 | } 23 | return &wallet.GetHeightResult{ 24 | Height: height, 25 | }, nil 26 | } 27 | -------------------------------------------------------------------------------- /docs/examples/derive_child/derive_child.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | ec "github.com/bsv-blockchain/go-sdk/primitives/ec" 7 | ) 8 | 9 | // example using BRC-42 method for deriving a child key 10 | func main() { 11 | merchantPrivKey, _ := ec.PrivateKeyFromWif("L4PoBVNHZb9wVs9TFqyFrKxmpkJPPyzbjQrCiiQUoCz7ceAq63Rt") 12 | 13 | invoiceNum := "test invoice number" 14 | 15 | customerPubKeyStr := "03121a7afe56fc8e25bca4bb2c94f35eb67ebe5b84df2e149d65b9423ee65b8b4b" 16 | customerPubKey, _ := ec.PublicKeyFromString(customerPubKeyStr) 17 | 18 | child, _ := merchantPrivKey.DeriveChild(customerPubKey, invoiceNum) 19 | 20 | fmt.Printf("%x", child.Serialize()) 21 | // now use the child key to sign a message, transaction, etc 22 | } 23 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/acquireCertificate-issuance-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "type": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0ZXN0LXR5cGU=", 4 | "certifier": "0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1", 5 | "acquisitionProtocol": "issuance", 6 | "fields": { 7 | "email": "alice@example.com", 8 | "name": "Alice" 9 | }, 10 | "certifierUrl": "https://certifier.example.com", 11 | "privileged": false 12 | }, 13 | "wire": "11000000000000000000000000000000000000000000000000746573742d747970650294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a10205656d61696c11616c696365406578616d706c652e636f6d046e616d6505416c69636500ff021d68747470733a2f2f6365727469666965722e6578616d706c652e636f6d" 14 | } -------------------------------------------------------------------------------- /primitives/ec/curve.go: -------------------------------------------------------------------------------- 1 | package primitives 2 | 3 | import "math/big" 4 | 5 | type CurveParams struct { 6 | P *big.Int // the order of the underlying field 7 | N *big.Int // the order of the base point 8 | B *big.Int // the constant of the curve equation 9 | Gx, Gy *big.Int // (x,y) of the base point 10 | BitSize int // the size of the underlying field 11 | Name string // the canonical name of the curve 12 | } 13 | 14 | type Curve interface { 15 | Params() *CurveParams 16 | IsOnCurve(x, y *big.Int) bool 17 | Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) 18 | Double(x1, y1 *big.Int) (*big.Int, *big.Int) 19 | ScalarMult(x1, y1 *big.Int, k []byte) (*big.Int, *big.Int) 20 | ScalarBaseMult(k []byte) (*big.Int, *big.Int) 21 | } 22 | -------------------------------------------------------------------------------- /docs/examples/broadcaster/broadcaster_gp/broadcaster_gp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/bsv-blockchain/go-sdk/transaction" 5 | "github.com/bsv-blockchain/go-sdk/transaction/broadcaster" 6 | ) 7 | 8 | func main() { 9 | 10 | // Create a new transaction 11 | hexTx := "010000000100" 12 | tx, _ := transaction.NewTransactionFromHex(hexTx) 13 | 14 | // Use the GP Arc Broadcaster 15 | 16 | b := &broadcaster.Arc{ 17 | ApiUrl: "https://arc.gorillapool.io", 18 | ApiKey: "", 19 | } 20 | 21 | // Broadcast the transaction 22 | success, failure := tx.Broadcast(b) 23 | 24 | // Check for errors 25 | if failure != nil { 26 | panic(failure) 27 | } 28 | 29 | // Print the success message and transaction ID 30 | println(success.Message, success.Txid) 31 | } 32 | -------------------------------------------------------------------------------- /docs/examples/broadcaster/broadcaster_woc/broadcaster_woc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/bsv-blockchain/go-sdk/transaction" 5 | "github.com/bsv-blockchain/go-sdk/transaction/broadcaster" 6 | ) 7 | 8 | func main() { 9 | 10 | // Create a new transaction 11 | hexTx := "010000000100" 12 | tx, _ := transaction.NewTransactionFromHex(hexTx) 13 | 14 | // Use the WOC API broadcaster 15 | b := &broadcaster.WhatsOnChain{ 16 | ApiKey: "", 17 | Network: broadcaster.WOCMainnet, 18 | } 19 | 20 | // Broadcast the transaction 21 | success, failure := tx.Broadcast(b) 22 | 23 | // Check for errors 24 | if failure != nil { 25 | panic(failure) 26 | } 27 | 28 | // Print the success message and transaction ID 29 | println(success.Message, success.Txid) 30 | } 31 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/createSignature-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "signature": [ 4 | 48, 5 | 37, 6 | 2, 7 | 32, 8 | 78, 9 | 69, 10 | 225, 11 | 105, 12 | 50, 13 | 184, 14 | 175, 15 | 81, 16 | 73, 17 | 97, 18 | 161, 19 | 211, 20 | 161, 21 | 162, 22 | 95, 23 | 223, 24 | 63, 25 | 79, 26 | 119, 27 | 50, 28 | 233, 29 | 214, 30 | 36, 31 | 198, 32 | 198, 33 | 21, 34 | 72, 35 | 171, 36 | 95, 37 | 184, 38 | 205, 39 | 65, 40 | 2, 41 | 1, 42 | 1 43 | ] 44 | }, 45 | "wire": "00302502204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41020101" 46 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore compiled binaries 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | *.test 7 | 8 | # Ignore dependencies 9 | /node_modules 10 | /vendor 11 | 12 | # Ignore build artifacts 13 | /build 14 | /dist 15 | 16 | # Ignore logs and temporary files 17 | /logs 18 | /tmp 19 | 20 | # Ignore IDE and editor specific files 21 | .idea 22 | *.sublime-project 23 | *.sublime-workspace 24 | 25 | # Ignore system files 26 | .DS_Store 27 | Thumbs.db 28 | 29 | # Ignore sensitive or environment-specific information 30 | .env 31 | .env.local 32 | .env.*.local 33 | .env.development 34 | .env.test 35 | .env.production 36 | 37 | # SonarCloud temporary files 38 | coverage.out 39 | golangci-lint-report.xml 40 | 41 | # Ignore any other files or directories specific to your project 42 | 43 | # Ignore files related to AI tools 44 | CLAUDE.md 45 | 46 | coverage.out -------------------------------------------------------------------------------- /docs/examples/keyshares_pk_to_backup/to_backup.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "log" 6 | 7 | ec "github.com/bsv-blockchain/go-sdk/primitives/ec" 8 | ) 9 | 10 | func main() { 11 | pk, _ := ec.PrivateKeyFromWif("KxPEP4DCP2a4g3YU5amfXjFH4kWmz8EHWrTugXocGWgWBbhGsX7a") 12 | log.Println("Private key:", hex.EncodeToString(pk.PubKey().Hash())[:8]) 13 | totalShares := 5 14 | threshold := 3 15 | shares, _ := pk.ToBackupShares(threshold, totalShares) 16 | 17 | for i, share := range shares { 18 | log.Printf("Share %d: %s", i+1, share) 19 | } 20 | 21 | // Prints 22 | // Share 1: ..3.bbc45478 23 | // Share 2: ..3.bbc45478 24 | // Share 3: ..3.bbc45478 25 | // Share 4: ..3.bbc45478 26 | // Share 5: ..3.bbc45478 27 | } 28 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Basic dependabot.yml to update gomod & GitHub Actions 2 | version: 2 3 | updates: 4 | # Maintain dependencies for the core library 5 | - package-ecosystem: "gomod" 6 | target-branch: "master" 7 | directory: "/" 8 | schedule: 9 | interval: "daily" 10 | time: "10:00" 11 | timezone: "America/New_York" 12 | assignees: 13 | - "shruggr" 14 | - "rohenaz" 15 | labels: 16 | - "chore" 17 | open-pull-requests-limit: 10 18 | 19 | # Maintain dependencies for GitHub Actions 20 | - package-ecosystem: "github-actions" 21 | target-branch: "master" 22 | directory: "/" 23 | schedule: 24 | interval: "weekly" 25 | day: "monday" 26 | assignees: 27 | - "shruggr" 28 | - "rohenaz" 29 | labels: 30 | - "chore" 31 | open-pull-requests-limit: 10 32 | -------------------------------------------------------------------------------- /docs/examples/ecies_electrum_binary/ecies_electrum_binary.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | ecies "github.com/bsv-blockchain/go-sdk/compat/ecies" 7 | ec "github.com/bsv-blockchain/go-sdk/primitives/ec" 8 | ) 9 | 10 | // Example of using ECIES to encrypt and decrypt data 11 | func main() { 12 | 13 | // user 1 14 | user1Pk, _ := ec.PrivateKeyFromWif("L211enC224G1kV8pyyq7bjVd9SxZebnRYEzzM3i7ZHCc1c5E7dQu") 15 | 16 | // user 2 17 | user2Pk, _ := ec.PublicKeyFromString("03121a7afe56fc8e25bca4bb2c94f35eb67ebe5b84df2e149d65b9423ee65b8b4b") 18 | 19 | encryptedData, _ := ecies.ElectrumEncrypt([]byte("hello world"), user2Pk, user1Pk, false) 20 | 21 | fmt.Println(string(encryptedData)) 22 | decryptedData, _ := ecies.ElectrumDecrypt(encryptedData, user1Pk, user2Pk) 23 | 24 | fmt.Printf("decryptedData: %s\n", decryptedData) 25 | 26 | } 27 | -------------------------------------------------------------------------------- /docs/examples/ecies_single/ecies_single.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | ecies "github.com/bsv-blockchain/go-sdk/compat/ecies" 7 | ec "github.com/bsv-blockchain/go-sdk/primitives/ec" 8 | ) 9 | 10 | // Example of using ECIES to encrypt and decrypt data for a single user 11 | func main() { 12 | myPrivateKey, _ := ec.PrivateKeyFromWif("L211enC224G1kV8pyyq7bjVd9SxZebnRYEzzM3i7ZHCc1c5E7dQu") 13 | 14 | encryptedData, _ := ecies.EncryptSingle("hello world", myPrivateKey) 15 | 16 | fmt.Println(encryptedData) 17 | // Prints: 18 | // QklFMQLoYyD2A6LA9Pd342B7Z5q4agY+r674wbq6Vu2YLtVqNU5RpP1SQZNkJ22FOQt9LmXHYgMFkORAJ1nD/JVGmbmmDCx4rbYfZBVh/aa9B4imlA== 19 | 20 | decryptedData, _ := ecies.DecryptSingle(encryptedData, myPrivateKey) 21 | 22 | fmt.Printf("decryptedData: %s\n", decryptedData) 23 | // Prints: 24 | // decryptedData: hello world 25 | } 26 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/listCertificates-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "certifiers": [ 4 | "0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1", 5 | "03b106dae20ae8fca0f4e8983d974c4b583054573eecdcdcfad261c035415ce1ee" 6 | ], 7 | "types": [ 8 | "dGVzdC10eXBlMSAgICAgICAgICAgICAgICAgICAgICA=", 9 | "dGVzdC10eXBlMiAgICAgICAgICAgICAgICAgICAgICA=" 10 | ], 11 | "limit": 5, 12 | "offset": 0, 13 | "privileged": true, 14 | "privilegedReason": "list-cert-reason" 15 | }, 16 | "wire": "1200020294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a103b106dae20ae8fca0f4e8983d974c4b583054573eecdcdcfad261c035415ce1ee02746573742d747970653120202020202020202020202020202020202020202020746573742d747970653220202020202020202020202020202020202020202020050001106c6973742d636572742d726561736f6e" 17 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/createAction-1-out-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "description": "Test action description", 4 | "outputs": [ 5 | { 6 | "lockingScript": "76a9143cf53c49c322d9d811728182939aee2dca087f9888ac", 7 | "satoshis": 999, 8 | "outputDescription": "Test output", 9 | "basket": "test-basket", 10 | "customInstructions": "Test instructions", 11 | "tags": [ 12 | "test-tag" 13 | ] 14 | } 15 | ], 16 | "labels": [ 17 | "test-label" 18 | ] 19 | }, 20 | "wire": "0100175465737420616374696f6e206465736372697074696f6effffffffffffffffffffffffffffffffffff011976a9143cf53c49c322d9d811728182939aee2dca087f9888acfde7030b54657374206f75747075740b746573742d6261736b6574115465737420696e737472756374696f6e730108746573742d746167ffffffffffffffffffffffffffffffffffff010a746573742d6c6162656c00" 21 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug or an issue you've found with `@bsv/sdk`. 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Bug Description 11 | 12 | Briefly describe the bug/issue you've encountered. 13 | 14 | ## Steps to Reproduce 15 | 16 | 1. Step 1 17 | 2. Step 2 18 | 3. ... 19 | 20 | ## Expected Behavior 21 | 22 | What should have happened if the bug hadn't occurred? 23 | 24 | ## Actual Behavior 25 | 26 | What actually happened? 27 | 28 | ## Stack Traces or Screenshots 29 | 30 | If applicable, add screenshots or stack traces to help explain the issue. 31 | 32 | ## Environment 33 | 34 | - OS: [e.g. MacOS, Windows] 35 | - Node version: [e.g. 12.20] 36 | - `@bsv/sdk` version: [e.g. 1.2.3] 37 | 38 | ## Additional Information 39 | 40 | Provide any additional context or information about the bug. 41 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description of Changes 2 | 3 | Provide a brief description of the changes you've made. 4 | 5 | ## Linked Issues / Tickets 6 | 7 | Reference any related issues or tickets, e.g. "Closes #123". 8 | 9 | ## Testing Procedure 10 | 11 | Describe the tests you've added or any testing steps you've taken. 12 | 13 | - [ ] I have added new unit tests 14 | - [ ] All tests pass locally 15 | - [ ] I have tested manually in my local environment 16 | - [ ] All tests pass when using `go test ./...` 17 | 18 | ## Checklist: 19 | 20 | - [ ] I have performed a self-review of my own code 21 | - [ ] I have made corresponding changes to the documentation 22 | - [ ] My changes generate no new warnings 23 | - [ ] I have updated `CHANGELOG.md` with my changes 24 | - [ ] I ran `golangci-lint run` 25 | - [ ] I have run `go fmt` and `go vet` one final time before requesting a review -------------------------------------------------------------------------------- /wallet/substrates/testdata/revealCounterpartyKeyLinkage-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "prover": "02e14bb4fbcd33d02a0bad2b60dcd14c36506fa15599e3c28ec87eff440a97a2b8", 4 | "counterparty": "0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1", 5 | "verifier": "03b106dae20ae8fca0f4e8983d974c4b583054573eecdcdcfad261c035415ce1ee", 6 | "revelationTime": "2023-01-01T00:00:00Z", 7 | "encryptedLinkage": [ 8 | 1, 9 | 2, 10 | 3, 11 | 4 12 | ], 13 | "encryptedLinkageProof": [ 14 | 5, 15 | 6, 16 | 7, 17 | 8 18 | ] 19 | }, 20 | "wire": "0002e14bb4fbcd33d02a0bad2b60dcd14c36506fa15599e3c28ec87eff440a97a2b803b106dae20ae8fca0f4e8983d974c4b583054573eecdcdcfad261c035415ce1ee0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a114323032332d30312d30315430303a30303a30305a04010203040405060708" 21 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/listOutputs-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "totalOutputs": 2, 4 | "BEEF": [ 5 | 1, 6 | 2, 7 | 3, 8 | 4 9 | ], 10 | "outputs": [ 11 | { 12 | "satoshis": 1000, 13 | "spendable": true, 14 | "outpoint": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef.0" 15 | }, 16 | { 17 | "satoshis": 5000, 18 | "spendable": true, 19 | "outpoint": "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890.2" 20 | } 21 | ] 22 | }, 23 | "wire": "000204010203041234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef00fde803ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffabcdef1234567890abcdef1234567890abcdef1234567890abcdef123456789002fd8813ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 24 | } -------------------------------------------------------------------------------- /transaction/broadcaster.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import "context" 4 | 5 | type BroadcastSuccess struct { 6 | Txid string `json:"txid"` 7 | Message string `json:"message"` 8 | } 9 | 10 | type BroadcastFailure struct { 11 | Code string `json:"code"` 12 | Description string `json:"description"` 13 | } 14 | 15 | func (e *BroadcastFailure) Error() string { 16 | return e.Description 17 | } 18 | 19 | type Broadcaster interface { 20 | Broadcast(tx *Transaction) (*BroadcastSuccess, *BroadcastFailure) 21 | BroadcastCtx(ctx context.Context, tx *Transaction) (*BroadcastSuccess, *BroadcastFailure) 22 | } 23 | 24 | func (t *Transaction) Broadcast(b Broadcaster) (*BroadcastSuccess, *BroadcastFailure) { 25 | return b.Broadcast(t) 26 | } 27 | 28 | func (t *Transaction) BroadcastCtx(ctx context.Context, b Broadcaster) (*BroadcastSuccess, *BroadcastFailure) { 29 | return b.BroadcastCtx(ctx, t) 30 | } 31 | -------------------------------------------------------------------------------- /docs/examples/keyshares_pk_from_backup/from_backup.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | ec "github.com/bsv-blockchain/go-sdk/primitives/ec" 7 | ) 8 | 9 | func main() { 10 | expectedWif := "KxPEP4DCP2a4g3YU5amfXjFH4kWmz8EHWrTugXocGWgWBbhGsX7a" 11 | 12 | // Restore a key from 3 of 5 key shares 13 | shares := []string{ 14 | "89Gtabj94hosNkJtAtSeJTBKrrZ2BpoVYr2Kmt5UFzjR.69DcY9ngWU7afbj1Na84BahFUMPb6qkBa1hmzDkDcp18.3.bbc45478", 15 | "CsA3JhDRqBb1z58FxoixZmdsLTvHuehfwZzPgqJVA3Yv.4PP6QQcmFxikiX38yYUCqE2LFmht2MjXkf4nRjMqYBgw.3.bbc45478", 16 | "BVk1tcvJEbhUfZagStg15rFRxQDeLzgSN15rWkGhNf19.CUB7p6zK3JPBkBriRRGdWj4y3Z3qCfsaCYutmMWKv1VJ.3.bbc45478", 17 | } 18 | 19 | pk, _ := ec.PrivateKeyFromBackupShares(shares) 20 | 21 | if pk.Wif() == expectedWif { 22 | log.Println("Private key:", pk.Wif()) 23 | } 24 | 25 | // Prints The Original Private key 26 | // KxPEP4DCP2a4g3YU5amfXjFH4kWmz8EHWrTugXocGWgWBbhGsX7a 27 | } 28 | -------------------------------------------------------------------------------- /primitives/ec/wif_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, 2014 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package primitives 6 | 7 | import ( 8 | "encoding/hex" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestEncodeDecodeWIF(t *testing.T) { 15 | mainWif := "Kxfd8ABTYZHBH3y1jToJ2AUJTMVbsNaqQsrkpo9gnnc1JXfBH8mn" 16 | testWif := "cP2cb5BJycySSVSH7scRPUyN5ao1XpgXUv1DwDcCHuG1ZGkJQxzH" 17 | privHex := "2b2afb5ea8d8c623acd6744547628988d86787003bb970afe15d471b727db79c" 18 | 19 | mainPriv, _ := PrivateKeyFromWif(mainWif) 20 | testPriv, _ := PrivateKeyFromWif(testWif) 21 | require.Equal(t, mainWif, mainPriv.Wif()) 22 | require.Equal(t, testWif, mainPriv.WifPrefix(byte(TestNet))) 23 | require.Equal(t, privHex, hex.EncodeToString(mainPriv.Serialize())) 24 | require.Equal(t, mainPriv.Serialize(), testPriv.Serialize()) 25 | } 26 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/revealSpecificKeyLinkage-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "encryptedLinkage": [ 4 | 1, 5 | 2, 6 | 3, 7 | 4 8 | ], 9 | "encryptedLinkageProof": [ 10 | 5, 11 | 6, 12 | 7, 13 | 8 14 | ], 15 | "prover": "02e14bb4fbcd33d02a0bad2b60dcd14c36506fa15599e3c28ec87eff440a97a2b8", 16 | "verifier": "03b106dae20ae8fca0f4e8983d974c4b583054573eecdcdcfad261c035415ce1ee", 17 | "counterparty": "0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1", 18 | "protocolID": [ 19 | 2, 20 | "tests" 21 | ], 22 | "keyID": "test-key-id", 23 | "proofType": 1 24 | }, 25 | "wire": "0002e14bb4fbcd33d02a0bad2b60dcd14c36506fa15599e3c28ec87eff440a97a2b803b106dae20ae8fca0f4e8983d974c4b583054573eecdcdcfad261c035415ce1ee0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1020574657374730b746573742d6b65792d69640401020304040506070801" 26 | } -------------------------------------------------------------------------------- /util/bytemanipulation.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "encoding/binary" 4 | 5 | // ReverseBytes reverses the bytes (little endian/big endian). 6 | // This is used when computing merkle trees in Bitcoin, for example. 7 | func ReverseBytes(a []byte) []byte { 8 | tmp := make([]byte, len(a)) 9 | copy(tmp, a) 10 | 11 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 12 | tmp[i], tmp[j] = tmp[j], tmp[i] 13 | } 14 | return tmp 15 | } 16 | 17 | // ReverseBytesInPlace reverses the bytes (little endian/big endian) in place (no extra memory allocation). 18 | func ReverseBytesInPlace(a []byte) { 19 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 20 | a[i], a[j] = a[j], a[i] 21 | } 22 | } 23 | 24 | // LittleEndianBytes returns a byte array in little endian from an unsigned integer of 32 bytes. 25 | func LittleEndianBytes(v uint32, l uint32) []byte { 26 | buf := make([]byte, 4) 27 | binary.LittleEndian.PutUint32(buf, v) 28 | return buf[:l] // Return only the first l bytes 29 | } 30 | -------------------------------------------------------------------------------- /docs/concepts/README.md: -------------------------------------------------------------------------------- 1 | # Conceptual Topics 2 | 3 | These documents cover high-level conceptual information that will augment developers' understanding of the code-level SDK documentation: 4 | 5 | - [What is BEEF and why is it useful?](BEEF.md) 6 | - [What is a Transaction Signature?](TX_SIG.md) 7 | - [What is Type-42 and How Does it Enable Private Signatures?](./42.md) 8 | - [What are Script Templates Used for?](TEMPLATES.md) 9 | - [How are Transactions Built with Inputs and Outputs?](HOW_TX.md) 10 | - [The Role of Chain Trackers within the SPV Ecosystem](CHAIN_SPV.md) 11 | - [How Does Transaction Fee Modeling Work?](FEE.md) 12 | - [How are Bitcoin Transactions Validated?](TX_VALID.md) 13 | - [Opcodes and Their Functionality Within Bitcoin Script](OP.md) 14 | - [What are Hashes and Why are they Important in Bitcoin?](HASHES.md) 15 | - [How does Authentication Work in the SDK?](AUTH.md) 16 | - [Overlay Tools](OVERLAY_TOOLS.md) 17 | - [Identity Concepts and Certificate Management](IDENTITY.md) 18 | -------------------------------------------------------------------------------- /transaction/consts.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | const ( 4 | // MaxTxInSequenceNum is the maximum sequence number the sequence field 5 | // of a transaction input can be. 6 | MaxTxInSequenceNum uint32 = 0xffffffff 7 | 8 | // MaxPrevOutIndex is the maximum index the index field of a previous 9 | // outpoint can be. 10 | MaxPrevOutIndex uint32 = 0xffffffff 11 | 12 | // SequenceLockTimeDisabled is a flag that if set on a transaction 13 | // input's sequence number, the sequence number will not be interpreted 14 | // as a relative locktime. 15 | SequenceLockTimeDisabled = 1 << 31 16 | 17 | // SequenceLockTimeIsSeconds is a flag that if set on a transaction 18 | // input's sequence number, the relative locktime has units of 512 19 | // seconds. 20 | SequenceLockTimeIsSeconds = 1 << 22 21 | 22 | // SequenceLockTimeMask is a mask that extracts the relative locktime 23 | // when masked against the transaction input sequence number. 24 | SequenceLockTimeMask = 0x0000ffff 25 | ) 26 | -------------------------------------------------------------------------------- /auth/transports/interface.go: -------------------------------------------------------------------------------- 1 | // Package transports provides abstractions for different communication protocols used in 2 | // authentication. It defines a common Transport interface that can be implemented by various 3 | // protocols such as HTTP and WebSocket, enabling flexible peer-to-peer communication patterns. 4 | // The package includes implementations for simplified HTTP transport and full-duplex WebSocket 5 | // transport, both supporting authenticated message exchange. 6 | package transports 7 | 8 | import ( 9 | "context" 10 | 11 | "github.com/bsv-blockchain/go-sdk/auth" 12 | ) 13 | 14 | // Transport defines the interface for communication transports used in authentication 15 | type Transport interface { 16 | // Send transmits an AuthMessage through the transport 17 | Send(ctx context.Context, message *auth.AuthMessage) error 18 | 19 | // OnData registers a callback function to handle incoming AuthMessages 20 | OnData(callback func(context.Context, *auth.AuthMessage) error) error 21 | } 22 | -------------------------------------------------------------------------------- /wallet/serializer/get_header.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/bsv-blockchain/go-sdk/util" 7 | "github.com/bsv-blockchain/go-sdk/wallet" 8 | ) 9 | 10 | func SerializeGetHeaderArgs(args *wallet.GetHeaderArgs) ([]byte, error) { 11 | w := util.NewWriter() 12 | w.WriteVarInt(uint64(args.Height)) 13 | return w.Buf, nil 14 | } 15 | 16 | func DeserializeGetHeaderArgs(data []byte) (*wallet.GetHeaderArgs, error) { 17 | 18 | r := util.NewReaderHoldError(data) 19 | 20 | args := &wallet.GetHeaderArgs{ 21 | Height: r.ReadVarInt32(), 22 | } 23 | 24 | r.CheckComplete() 25 | if r.Err != nil { 26 | return nil, fmt.Errorf("error deserializing GetHeaderArgs: %w", r.Err) 27 | } 28 | return args, nil 29 | } 30 | 31 | func SerializeGetHeaderResult(result *wallet.GetHeaderResult) ([]byte, error) { 32 | return result.Header, nil 33 | } 34 | 35 | func DeserializeGetHeaderResult(data []byte) (*wallet.GetHeaderResult, error) { 36 | return &wallet.GetHeaderResult{ 37 | Header: data, 38 | }, nil 39 | } 40 | -------------------------------------------------------------------------------- /chainhash/hashfuncs.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 The Decred developers 2 | // Copyright (c) 2016-2017 The btcsuite developers 3 | // Use of this source code is governed by an ISC 4 | // license that can be found in the LICENSE file. 5 | 6 | package chainhash 7 | 8 | import "crypto/sha256" 9 | 10 | // HashB calculates hash(b) and returns the resulting bytes. 11 | func HashB(b []byte) []byte { 12 | hash := sha256.Sum256(b) 13 | return hash[:] 14 | } 15 | 16 | // HashH calculates hash(b) and returns the resulting bytes as a Hash. 17 | func HashH(b []byte) Hash { 18 | return Hash(sha256.Sum256(b)) 19 | } 20 | 21 | // DoubleHashB calculates hash(hash(b)) and returns the resulting bytes. 22 | func DoubleHashB(b []byte) []byte { 23 | first := sha256.Sum256(b) 24 | second := sha256.Sum256(first[:]) 25 | return second[:] 26 | } 27 | 28 | // DoubleHashH calculates hash(hash(b)) and returns the resulting bytes as a 29 | // Hash. 30 | func DoubleHashH(b []byte) Hash { 31 | first := sha256.Sum256(b) 32 | return Hash(sha256.Sum256(first[:])) 33 | } 34 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "sonarlint.connectedMode.project": { 3 | "connectionId": "bitcoin-sv", 4 | "projectKey": "bitcoin-sv_go-sdk" 5 | }, 6 | "workbench.colorCustomizations": { 7 | "activityBar.activeBackground": "#3399ff", 8 | "activityBar.background": "#3399ff", 9 | "activityBar.foreground": "#15202b", 10 | "activityBar.inactiveForeground": "#15202b99", 11 | "activityBarBadge.background": "#bf0060", 12 | "activityBarBadge.foreground": "#e7e7e7", 13 | "commandCenter.border": "#e7e7e799", 14 | "sash.hoverBorder": "#3399ff", 15 | "statusBar.background": "#007fff", 16 | "statusBar.foreground": "#e7e7e7", 17 | "statusBarItem.hoverBackground": "#3399ff", 18 | "statusBarItem.remoteBackground": "#007fff", 19 | "statusBarItem.remoteForeground": "#e7e7e7", 20 | "titleBar.activeBackground": "#007fff", 21 | "titleBar.activeForeground": "#e7e7e7", 22 | "titleBar.inactiveBackground": "#007fff99", 23 | "titleBar.inactiveForeground": "#e7e7e799" 24 | }, 25 | "peacock.color": "#007fff" 26 | } -------------------------------------------------------------------------------- /wallet/serializer/get_network_test.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "github.com/bsv-blockchain/go-sdk/wallet" 5 | "github.com/stretchr/testify/require" 6 | "testing" 7 | ) 8 | 9 | func TestGetNetworkResult(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | result *wallet.GetNetworkResult 13 | }{ 14 | { 15 | name: "mainnet", 16 | result: &wallet.GetNetworkResult{ 17 | Network: wallet.NetworkMainnet, 18 | }, 19 | }, 20 | { 21 | name: "testnet", 22 | result: &wallet.GetNetworkResult{ 23 | Network: wallet.NetworkTestnet, 24 | }, 25 | }, 26 | } 27 | 28 | for _, tt := range tests { 29 | t.Run(tt.name, func(t *testing.T) { 30 | // Test serialization 31 | data, err := SerializeGetNetworkResult(tt.result) 32 | require.NoError(t, err) 33 | require.Equal(t, 1, len(data)) // error byte + network byte 34 | 35 | // Test deserialization 36 | got, err := DeserializeGetNetworkResult(data) 37 | require.NoError(t, err) 38 | require.Equal(t, tt.result, got) 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/verifyHmac-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "protocolID": [ 4 | 1, 5 | "test-protocol" 6 | ], 7 | "keyID": "test-key", 8 | "counterparty": "self", 9 | "privileged": true, 10 | "privilegedReason": "test reason", 11 | "seekPermission": true, 12 | "data": [ 13 | 10, 14 | 20, 15 | 30, 16 | 40 17 | ], 18 | "hmac": [ 19 | 50, 20 | 60, 21 | 70, 22 | 80, 23 | 90, 24 | 100, 25 | 110, 26 | 120, 27 | 50, 28 | 60, 29 | 70, 30 | 80, 31 | 90, 32 | 100, 33 | 110, 34 | 120, 35 | 50, 36 | 60, 37 | 70, 38 | 80, 39 | 90, 40 | 100, 41 | 110, 42 | 120, 43 | 50, 44 | 60, 45 | 70, 46 | 80, 47 | 90, 48 | 100, 49 | 110, 50 | 120 51 | ] 52 | }, 53 | "wire": "0e00010d746573742d70726f746f636f6c08746573742d6b65790b010b7465737420726561736f6e323c46505a646e78323c46505a646e78323c46505a646e78323c46505a646e78040a141e2801" 54 | } -------------------------------------------------------------------------------- /docs/examples/encrypted_message/message.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/bsv-blockchain/go-sdk/message" 7 | ec "github.com/bsv-blockchain/go-sdk/primitives/ec" 8 | ) 9 | 10 | func main() { 11 | sender, _ := ec.NewPrivateKey() 12 | recipient, _ := ec.NewPrivateKey() 13 | 14 | msg := []byte{1, 2, 4, 8, 16, 32} 15 | 16 | // Encrypt a message using the sender's private key and the recipient's public key 17 | encryptedData, _ := message.Encrypt(msg, sender, recipient.PubKey()) 18 | 19 | // Decrypt a message using the private key of the recipient 20 | decryptedData, _ := message.Decrypt(encryptedData, recipient) 21 | 22 | fmt.Printf("decryptedData: %s\n", decryptedData) 23 | 24 | // sign message for a recipient 25 | signature, _ := message.Sign(msg, sender, recipient.PubKey()) 26 | verified, _ := message.Verify(msg, signature, recipient) 27 | fmt.Printf("verified: %t\n", verified) 28 | 29 | // sign a message for anyone 30 | signature, _ = message.Sign(msg, sender, nil) 31 | verified, _ = message.Verify(msg, signature, nil) 32 | fmt.Printf("verified: %t\n", verified) 33 | } 34 | -------------------------------------------------------------------------------- /wallet/serializer/abort_action.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/bsv-blockchain/go-sdk/util" 7 | "github.com/bsv-blockchain/go-sdk/wallet" 8 | ) 9 | 10 | func SerializeAbortActionArgs(args *wallet.AbortActionArgs) ([]byte, error) { 11 | w := util.NewWriter() 12 | 13 | // Serialize reference 14 | w.WriteBytes(args.Reference) 15 | 16 | return w.Buf, nil 17 | } 18 | 19 | func DeserializeAbortActionArgs(data []byte) (*wallet.AbortActionArgs, error) { 20 | r := util.NewReaderHoldError(data) 21 | args := &wallet.AbortActionArgs{} 22 | 23 | // Read reference 24 | args.Reference = r.ReadRemaining() 25 | 26 | if r.Err != nil { 27 | return nil, fmt.Errorf("error reading abort action args: %w", r.Err) 28 | } 29 | 30 | return args, nil 31 | } 32 | 33 | func SerializeAbortActionResult(*wallet.AbortActionResult) ([]byte, error) { 34 | // Frame indicates error or not, no additional data 35 | return nil, nil 36 | } 37 | 38 | func DeserializeAbortActionResult([]byte) (*wallet.AbortActionResult, error) { 39 | // Accepted is implicit 40 | return &wallet.AbortActionResult{ 41 | Aborted: true, 42 | }, nil 43 | } 44 | -------------------------------------------------------------------------------- /wallet/serializer/authenticated.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/bsv-blockchain/go-sdk/util" 7 | "github.com/bsv-blockchain/go-sdk/wallet" 8 | ) 9 | 10 | func SerializeIsAuthenticatedResult(result *wallet.AuthenticatedResult) ([]byte, error) { 11 | w := util.NewWriter() 12 | 13 | // Authenticated flag (1=true, 0=false) 14 | if result.Authenticated { 15 | w.WriteByte(1) 16 | } else { 17 | w.WriteByte(0) 18 | } 19 | 20 | return w.Buf, nil 21 | } 22 | 23 | func DeserializeIsAuthenticatedResult(data []byte) (*wallet.AuthenticatedResult, error) { 24 | if len(data) != 1 { 25 | return nil, fmt.Errorf("invalid data length for authenticated result") 26 | } 27 | 28 | // Second byte is authenticated flag 29 | result := &wallet.AuthenticatedResult{ 30 | Authenticated: data[0] == 1, 31 | } 32 | 33 | return result, nil 34 | } 35 | 36 | func SerializeWaitAuthenticatedResult(_ *wallet.AuthenticatedResult) ([]byte, error) { 37 | return nil, nil 38 | } 39 | 40 | func DeserializeWaitAuthenticatedResult(_ []byte) (*wallet.AuthenticatedResult, error) { 41 | return &wallet.AuthenticatedResult{ 42 | Authenticated: true, 43 | }, nil 44 | } 45 | -------------------------------------------------------------------------------- /wallet/serializer/get_network.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/bsv-blockchain/go-sdk/util" 7 | "github.com/bsv-blockchain/go-sdk/wallet" 8 | ) 9 | 10 | const ( 11 | networkMainnetCode = 0 12 | networkTestnetCode = 1 13 | ) 14 | 15 | func SerializeGetNetworkResult(result *wallet.GetNetworkResult) ([]byte, error) { 16 | w := util.NewWriter() 17 | 18 | // Network byte (0 for mainnet, 1 for testnet) 19 | if result.Network == wallet.NetworkMainnet { 20 | w.WriteByte(networkMainnetCode) 21 | } else { 22 | w.WriteByte(networkTestnetCode) 23 | } 24 | 25 | return w.Buf, nil 26 | } 27 | 28 | func DeserializeGetNetworkResult(data []byte) (*wallet.GetNetworkResult, error) { 29 | r := util.NewReaderHoldError(data) 30 | 31 | // Read network byte 32 | result := new(wallet.GetNetworkResult) 33 | switch r.ReadByte() { 34 | case networkMainnetCode: 35 | result.Network = wallet.NetworkMainnet 36 | case networkTestnetCode: 37 | result.Network = wallet.NetworkTestnet 38 | } 39 | 40 | r.CheckComplete() 41 | if r.Err != nil { 42 | return nil, fmt.Errorf("error reading get network result: %w", r.Err) 43 | } 44 | 45 | return result, nil 46 | } 47 | -------------------------------------------------------------------------------- /transaction/merkletreeparent_test.go: -------------------------------------------------------------------------------- 1 | package transaction_test 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | 7 | "github.com/bsv-blockchain/go-sdk/transaction" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestGetMerkleTreeParentStr(t *testing.T) { 12 | leftNode := "d6c79a6ef05572f0cb8e9a450c561fc40b0a8a7d48faad95e20d93ddeb08c231" 13 | rightNode := "b1ed931b79056438b990d8981ba46fae97e5574b142445a74a44b978af284f98" 14 | 15 | expected := "b0d537b3ee52e472507f453df3d69561720346118a5a8c4d85ca0de73bc792be" 16 | 17 | parent, err := transaction.MerkleTreeParentStr(leftNode, rightNode) 18 | 19 | require.NoError(t, err) 20 | require.Equal(t, expected, parent) 21 | } 22 | 23 | func TestGetMerkleTreeParent(t *testing.T) { 24 | leftNode, _ := hex.DecodeString("d6c79a6ef05572f0cb8e9a450c561fc40b0a8a7d48faad95e20d93ddeb08c231") 25 | rightNode, _ := hex.DecodeString("b1ed931b79056438b990d8981ba46fae97e5574b142445a74a44b978af284f98") 26 | 27 | expected, _ := hex.DecodeString("b0d537b3ee52e472507f453df3d69561720346118a5a8c4d85ca0de73bc792be") 28 | 29 | parent := transaction.MerkleTreeParentBytes(leftNode, rightNode) 30 | 31 | require.Equal(t, expected, parent) 32 | } 33 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/listActions-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "totalActions": 1, 4 | "actions": [ 5 | { 6 | "txid": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", 7 | "satoshis": 1000, 8 | "status": "completed", 9 | "isOutgoing": true, 10 | "description": "Test transaction 1", 11 | "version": 1, 12 | "lockTime": 10, 13 | "outputs": [ 14 | { 15 | "satoshis": 1000, 16 | "lockingScript": "76a9143cf53c49c322d9d811728182939aee2dca087f9888ac", 17 | "spendable": true, 18 | "tags": [ 19 | "tag1", 20 | "tag2" 21 | ], 22 | "outputIndex": 1, 23 | "outputDescription": "Test output", 24 | "basket": "basket1" 25 | } 26 | ] 27 | } 28 | ] 29 | }, 30 | "wire": "00011234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdeffde80301011254657374207472616e73616374696f6e2031ffffffffffffffffff010affffffffffffffffff0101fde8031976a9143cf53c49c322d9d811728182939aee2dca087f9888ac010b54657374206f7574707574076261736b6574310204746167310474616732ffffffffffffffffff" 31 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/verifySignature-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "protocolID": [ 4 | 1, 5 | "test-protocol" 6 | ], 7 | "keyID": "test-key", 8 | "counterparty": "self", 9 | "privileged": true, 10 | "privilegedReason": "test reason", 11 | "seekPermission": true, 12 | "data": [ 13 | 11, 14 | 22, 15 | 33, 16 | 44 17 | ], 18 | "signature": [ 19 | 48, 20 | 37, 21 | 2, 22 | 32, 23 | 78, 24 | 69, 25 | 225, 26 | 105, 27 | 50, 28 | 184, 29 | 175, 30 | 81, 31 | 73, 32 | 97, 33 | 161, 34 | 211, 35 | 161, 36 | 162, 37 | 95, 38 | 223, 39 | 63, 40 | 79, 41 | 119, 42 | 50, 43 | 233, 44 | 214, 45 | 36, 46 | 198, 47 | 198, 48 | 21, 49 | 72, 50 | 171, 51 | 95, 52 | 184, 53 | 205, 54 | 65, 55 | 2, 56 | 1, 57 | 1 58 | ] 59 | }, 60 | "wire": "1000010d746573742d70726f746f636f6c08746573742d6b65790b010b7465737420726561736f6eff27302502204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd4102010101040b16212c01" 61 | } -------------------------------------------------------------------------------- /docs/examples/GO_BT.md: -------------------------------------------------------------------------------- 1 | # Convert from libsv/go-bt transaction 2 | 3 | For users of libsv/go-bt library, this function illustrates the differences between the two transaction structures and converts from one to the other. 4 | 5 | ```go 6 | func GoBt2GoSDKTransaction(tx *bt.Tx) *transaction.Transaction { 7 | sdkTx := &transaction.Transaction{ 8 | Version: tx.Version, 9 | LockTime: tx.LockTime, 10 | } 11 | 12 | sdkTx.Inputs = make([]*transaction.TransactionInput, len(tx.Inputs)) 13 | for i, in := range tx.Inputs { 14 | sdkTx.Inputs[i] = &transaction.TransactionInput{ 15 | SourceTXID: bt.ReverseBytes(in.PreviousTxID()), 16 | SourceTxOutIndex: in.PreviousTxOutIndex, 17 | UnlockingScript: (*script.Script)(in.UnlockingScript), 18 | SequenceNumber: in.SequenceNumber, 19 | } 20 | } 21 | 22 | sdkTx.Outputs = make([]*transaction.TransactionOutput, len(tx.Outputs)) 23 | for i, out := range tx.Outputs { 24 | sdkTx.Outputs[i] = &transaction.TransactionOutput{ 25 | Satoshis: out.Satoshis, 26 | LockingScript: (*script.Script)(out.LockingScript), 27 | } 28 | } 29 | 30 | return sdkTx 31 | } 32 | ``` -------------------------------------------------------------------------------- /wallet/substrates/testdata/internalizeAction-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "tx": [ 4 | 1, 5 | 2, 6 | 3, 7 | 4 8 | ], 9 | "description": "test transaction", 10 | "labels": [ 11 | "label1", 12 | "label2" 13 | ], 14 | "seekPermission": true, 15 | "outputs": [ 16 | { 17 | "outputIndex": 0, 18 | "protocol": "wallet payment", 19 | "paymentRemittance": { 20 | "derivationPrefix": "cHJlZml4", 21 | "derivationSuffix": "c3VmZml4", 22 | "senderIdentityKey": "03b106dae20ae8fca0f4e8983d974c4b583054573eecdcdcfad261c035415ce1ee" 23 | } 24 | }, 25 | { 26 | "outputIndex": 1, 27 | "protocol": "basket insertion", 28 | "insertionRemittance": { 29 | "basket": "test-basket", 30 | "customInstructions": "instruction", 31 | "tags": [ 32 | "tag1", 33 | "tag2" 34 | ] 35 | } 36 | } 37 | ] 38 | }, 39 | "wire": "0500040102030402000103b106dae20ae8fca0f4e8983d974c4b583054573eecdcdcfad261c035415ce1ee067072656669780673756666697801020b746573742d6261736b65740b696e737472756374696f6e020474616731047461673202066c6162656c31066c6162656c321074657374207472616e73616374696f6e01" 40 | } -------------------------------------------------------------------------------- /wallet/serializer/certificate_test.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "testing" 5 | 6 | ec "github.com/bsv-blockchain/go-sdk/primitives/ec" 7 | tu "github.com/bsv-blockchain/go-sdk/util/test_util" 8 | "github.com/bsv-blockchain/go-sdk/wallet" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestCertificate(t *testing.T) { 13 | t.Run("serialize/deserialize", func(t *testing.T) { 14 | sig := tu.GetSigFromHex(t, "3045022100a6f09ee70382ab364f3f6b040aebb8fe7a51dbc3b4c99cfeb2f7756432162833022067349b91a6319345996faddf36d1b2f3a502e4ae002205f9d2db85474f9aed5a") 15 | pk, err := ec.NewPrivateKey() 16 | require.NoError(t, err) 17 | cert := &wallet.Certificate{ 18 | Subject: pk.PubKey(), 19 | Certifier: pk.PubKey(), 20 | RevocationOutpoint: tu.OutpointFromString(t, "a755810c21e17183ff6db6685f0de239fd3a0a3c0d4ba7773b0b0d1748541e2b.0"), 21 | Signature: sig, 22 | Fields: map[string]string{ 23 | "field1": "value1", 24 | "field2": "value2", 25 | }, 26 | } 27 | copy(cert.Type[:], []byte("test-cert")) 28 | 29 | data, err := SerializeCertificate(cert) 30 | require.NoError(t, err) 31 | 32 | got, err := DeserializeCertificate(data) 33 | require.NoError(t, err) 34 | require.Equal(t, cert, got) 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /docs/examples/create_tx_with_op_return/create_tx_with_op_return.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/bsv-blockchain/go-sdk/chainhash" 7 | ec "github.com/bsv-blockchain/go-sdk/primitives/ec" 8 | "github.com/bsv-blockchain/go-sdk/script" 9 | "github.com/bsv-blockchain/go-sdk/transaction" 10 | "github.com/bsv-blockchain/go-sdk/transaction/template/p2pkh" 11 | ) 12 | 13 | func main() { 14 | priv, _ := ec.PrivateKeyFromWif("L3VJH2hcRGYYG6YrbWGmsxQC1zyYixA82YjgEyrEUWDs4ALgk8Vu") 15 | 16 | tx := transaction.NewTransaction() 17 | p2pkh, err := p2pkh.Unlock(priv, nil) 18 | if err != nil { 19 | log.Fatal(err.Error()) 20 | } 21 | 22 | txid, _ := chainhash.NewHashFromHex("b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576") 23 | s, _ := script.NewFromHex("76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac") 24 | tx.AddInputWithOutput(&transaction.TransactionInput{ 25 | SourceTXID: txid, 26 | SourceTxOutIndex: 0, 27 | UnlockingScriptTemplate: p2pkh, 28 | }, &transaction.TransactionOutput{ 29 | LockingScript: s, 30 | Satoshis: 1000, 31 | }) 32 | _ = tx.AddOpReturnOutput([]byte("You are using go-sdk!")) 33 | 34 | if err := tx.Sign(); err != nil { 35 | log.Fatal(err.Error()) 36 | } 37 | log.Println("tx: ", tx.String()) 38 | } 39 | -------------------------------------------------------------------------------- /util/bytestring.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/hex" 6 | "encoding/json" 7 | "errors" 8 | ) 9 | 10 | // ByteString is a byte array that serializes to hex 11 | type ByteString []byte 12 | 13 | func NewByteStringFromHex(s string) ByteString { 14 | b, _ := hex.DecodeString(s) 15 | return ByteString(b) 16 | } 17 | 18 | // MarshalJSON serializes ByteArray to hex 19 | func (s ByteString) MarshalJSON() ([]byte, error) { 20 | return json.Marshal(s.String()) 21 | } 22 | 23 | // UnmarshalJSON deserializes ByteArray to hex 24 | func (s *ByteString) UnmarshalJSON(data []byte) error { 25 | var x string 26 | err := json.Unmarshal(data, &x) 27 | if err != nil { 28 | return err 29 | } 30 | if len(x) > 0 { 31 | str, err := hex.DecodeString(x) 32 | if err != nil { 33 | return err 34 | } 35 | *s = ByteString(str) 36 | } else { 37 | *s = nil 38 | } 39 | return nil 40 | } 41 | 42 | func (s *ByteString) String() string { 43 | return hex.EncodeToString(*s) 44 | } 45 | 46 | func (s ByteString) Value() (driver.Value, error) { 47 | return []byte(s), nil 48 | } 49 | 50 | func (s *ByteString) Scan(value any) error { 51 | b, ok := value.([]byte) 52 | if !ok { 53 | return errors.New("type assertion to []byte failed") 54 | } 55 | *s = b 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/acquireCertificate-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "type": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0ZXN0LXR5cGU=", 4 | "serialNumber": "AAAAAAAAAAAAAAAAAAB0ZXN0LXNlcmlhbC1udW1iZXI=", 5 | "subject": "025ad43a22ac38d0bc1f8bacaabb323b5d634703b7a774c4268f6a09e4ddf79097", 6 | "certifier": "0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1", 7 | "revocationOutpoint": "aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d.0", 8 | "fields": { 9 | "email": "alice@example.com", 10 | "name": "Alice" 11 | }, 12 | "signature": "3045022100a6f09ee70382ab364f3f6b040aebb8fe7a51dbc3b4c99cfeb2f7756432162833022067349b91a6319345996faddf36d1b2f3a502e4ae002205f9d2db85474f9aed5a" 13 | }, 14 | "wire": "000000000000000000000000000000000000000000000000746573742d747970650000000000000000000000000000746573742d73657269616c2d6e756d626572025ad43a22ac38d0bc1f8bacaabb323b5d634703b7a774c4268f6a09e4ddf790970294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d000205656d61696c11616c696365406578616d706c652e636f6d046e616d6505416c6963653045022100a6f09ee70382ab364f3f6b040aebb8fe7a51dbc3b4c99cfeb2f7756432162833022067349b91a6319345996faddf36d1b2f3a502e4ae002205f9d2db85474f9aed5a" 15 | } -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yaml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "master" 7 | push: 8 | branches: 9 | - "master" 10 | 11 | env: 12 | GO_VERSION: '1.24' 13 | GOLANGCI_LINT_VERSION: v2.1.6 14 | 15 | jobs: 16 | detect-modules: 17 | runs-on: ubuntu-latest 18 | outputs: 19 | modules: ${{ steps.set-modules.outputs.modules }} 20 | steps: 21 | - uses: actions/checkout@v6 22 | - uses: actions/setup-go@v6 23 | with: 24 | go-version: ${{ env.GO_VERSION }} 25 | - id: set-modules 26 | run: echo "modules=$(go list -m -json | jq -s '.' | jq -c '[.[].Dir]')" >> $GITHUB_OUTPUT 27 | 28 | golangci-lint: 29 | needs: detect-modules 30 | runs-on: ubuntu-latest 31 | strategy: 32 | matrix: 33 | modules: ${{ fromJSON(needs.detect-modules.outputs.modules) }} 34 | steps: 35 | - uses: actions/checkout@v6 36 | - uses: actions/setup-go@v6 37 | with: 38 | go-version: ${{ env.GO_VERSION }} 39 | - name: golangci-lint ${{ matrix.modules }} 40 | uses: golangci/golangci-lint-action@v9 41 | with: 42 | version: ${{ env.GOLANGCI_LINT_VERSION }} 43 | working-directory: ${{ matrix.modules }} 44 | args: --path-prefix=${{ matrix.modules }}/ -------------------------------------------------------------------------------- /transaction/fee_model/sats_per_kb.go: -------------------------------------------------------------------------------- 1 | package feemodel 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/bsv-blockchain/go-sdk/transaction" 7 | "github.com/bsv-blockchain/go-sdk/util" 8 | ) 9 | 10 | type SatoshisPerKilobyte struct { 11 | Satoshis uint64 12 | } 13 | 14 | func (s *SatoshisPerKilobyte) ComputeFee(tx *transaction.Transaction) (uint64, error) { 15 | size := 4 16 | size += util.VarInt(len(tx.Inputs)).Length() 17 | for vin, i := range tx.Inputs { 18 | size += 40 19 | if i.UnlockingScript != nil && len(*i.UnlockingScript) > 0 { 20 | scriptLen := len(*i.UnlockingScript) 21 | size += util.VarInt(scriptLen).Length() + scriptLen 22 | } else if i.UnlockingScriptTemplate != nil { 23 | scriptLen := int(i.UnlockingScriptTemplate.EstimateLength(tx, uint32(vin))) 24 | size += util.VarInt(scriptLen).Length() + scriptLen 25 | } else { 26 | return 0, ErrNoUnlockingScript 27 | } 28 | } 29 | size += util.VarInt(len(tx.Outputs)).Length() 30 | for _, o := range tx.Outputs { 31 | size += 8 32 | size += util.VarInt(len(*o.LockingScript)).Length() 33 | size += len(*o.LockingScript) 34 | } 35 | size += 4 36 | return calculateFee(size, s.Satoshis), nil 37 | } 38 | 39 | func calculateFee(txSizeBytes int, satoshisPerKB uint64) uint64 { 40 | return uint64(math.Ceil(float64(txSizeBytes) / 1000 * float64(satoshisPerKB))) 41 | } 42 | -------------------------------------------------------------------------------- /transaction/utxo.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "github.com/bsv-blockchain/go-sdk/chainhash" 5 | script "github.com/bsv-blockchain/go-sdk/script" 6 | ) 7 | 8 | // UTXO an unspent transaction output, used for creating inputs 9 | type UTXO struct { 10 | TxID *chainhash.Hash `json:"txid"` 11 | Vout uint32 `json:"vout"` 12 | LockingScript *script.Script `json:"locking_script"` 13 | Satoshis uint64 `json:"satoshis"` 14 | UnlockingScriptTemplate UnlockingScriptTemplate `json:"-"` 15 | } 16 | 17 | // UTXOs a collection of *transaction.UTXO. 18 | type UTXOs []*UTXO 19 | 20 | // LockingScriptHex retur nthe locking script in hex format. 21 | func (u *UTXO) LockingScriptHex() string { 22 | return u.LockingScript.String() 23 | } 24 | 25 | // NewUTXO creates a new UTXO. 26 | func NewUTXO(prevTxID string, vout uint32, prevTxLockingScript string, satoshis uint64) (*UTXO, error) { 27 | pts, err := script.NewFromHex(prevTxLockingScript) 28 | if err != nil { 29 | return nil, err 30 | } 31 | pti, err := chainhash.NewHashFromHex(prevTxID) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | return &UTXO{ 37 | TxID: pti, 38 | Vout: vout, 39 | LockingScript: pts, 40 | Satoshis: satoshis, 41 | }, nil 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yaml: -------------------------------------------------------------------------------- 1 | name: Test and Coverage 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "master" 7 | push: 8 | branches: 9 | - "master" 10 | 11 | env: 12 | GO_VERSION: '1.24' 13 | 14 | jobs: 15 | test-coverage: 16 | name: Test and Upload Coverage 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v6 21 | with: 22 | fetch-depth: 2 23 | 24 | - name: Setup Go 25 | uses: actions/setup-go@v6 26 | with: 27 | go-version: ${{ env.GO_VERSION }} 28 | 29 | - name: Download dependencies 30 | run: go mod download 31 | 32 | - name: Run tests with coverage 33 | run: | 34 | go test -race -coverprofile=coverage.out -covermode=atomic ./... 35 | 36 | - name: Upload coverage to Codecov 37 | uses: codecov/codecov-action@v5 38 | with: 39 | fail_ci_if_error: true 40 | files: ./coverage.out 41 | flags: unittests 42 | name: codecov-umbrella 43 | token: ${{ secrets.CODECOV_TOKEN }} 44 | verbose: true 45 | 46 | - name: Upload coverage reports as artifacts 47 | uses: actions/upload-artifact@v5 48 | with: 49 | name: coverage-report 50 | path: coverage.out 51 | retention-days: 7 -------------------------------------------------------------------------------- /overlay/lookup/types.go: -------------------------------------------------------------------------------- 1 | package lookup 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/bsv-blockchain/go-sdk/transaction" 6 | ) 7 | 8 | // AnswerType represents the type of answer returned by a lookup service 9 | type AnswerType string 10 | 11 | var ( 12 | AnswerTypeOutputList AnswerType = "output-list" 13 | AnswerTypeFreeform AnswerType = "freeform" 14 | AnswerTypeFormula AnswerType = "formula" 15 | ) 16 | 17 | // OutputListItem represents a transaction output with its BEEF and output index 18 | type OutputListItem struct { 19 | Beef []byte `json:"beef"` 20 | OutputIndex uint32 `json:"outputIndex"` 21 | } 22 | 23 | // LookupQuestion represents a question asked to an overlay lookup service 24 | type LookupQuestion struct { 25 | Service string `json:"service"` 26 | Query json.RawMessage `json:"query"` 27 | } 28 | 29 | // LookupFormula represents a formula for computing lookup results 30 | type LookupFormula struct { 31 | Outpoint *transaction.Outpoint 32 | History func(beef []byte, outputIndex uint32, currentDepth uint32) bool 33 | // HistoryDepth uint32 34 | } 35 | 36 | // LookupAnswer represents the response from an overlay lookup service 37 | type LookupAnswer struct { 38 | Type AnswerType `json:"type"` 39 | Outputs []*OutputListItem `json:"outputs,omitempty"` 40 | Formulas []LookupFormula `json:"-"` 41 | Result any `json:"result,omitempty"` 42 | } 43 | -------------------------------------------------------------------------------- /wallet/serializer/get_version_test.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "github.com/bsv-blockchain/go-sdk/wallet" 5 | "github.com/stretchr/testify/require" 6 | "testing" 7 | ) 8 | 9 | func TestGetVersionResult(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | result *wallet.GetVersionResult 13 | }{ 14 | { 15 | name: "standard version", 16 | result: &wallet.GetVersionResult{ 17 | Version: "1.2.3", 18 | }, 19 | }, 20 | { 21 | name: "long version", 22 | result: &wallet.GetVersionResult{ 23 | Version: "v2.5.1-beta+12345", 24 | }, 25 | }, 26 | { 27 | name: "empty version", 28 | result: &wallet.GetVersionResult{ 29 | Version: "", 30 | }, 31 | }, 32 | } 33 | 34 | for _, tt := range tests { 35 | t.Run(tt.name, func(t *testing.T) { 36 | // Test serialization 37 | data, err := SerializeGetVersionResult(tt.result) 38 | require.NoError(t, err, "serializing GetVersionResult should not error") 39 | if tt.result.Version != "" { 40 | require.GreaterOrEqual(t, len(data), 1, "serialized data should have at least error byte") // At least error byte 41 | } 42 | 43 | // Test deserialization 44 | got, err := DeserializeGetVersionResult(data) 45 | require.NoError(t, err, "deserializing GetVersionResult should not error") 46 | require.Equal(t, tt.result, got, "deserialized result should match original result") 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /primitives/hash/hash.go: -------------------------------------------------------------------------------- 1 | package primitives 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha256" 6 | "crypto/sha512" 7 | 8 | "golang.org/x/crypto/ripemd160" // nolint:staticcheck // required 9 | ) 10 | 11 | // Sha256 calculates hash(b) and returns the resulting bytes. 12 | func Sha256(b []byte) []byte { 13 | data := sha256.Sum256(b) 14 | return data[:] 15 | } 16 | 17 | // Sha256d calculates hash(hash(b)) and returns the resulting bytes. 18 | func Sha256d(b []byte) []byte { 19 | first := Sha256(b) 20 | return Sha256(first[:]) 21 | } 22 | 23 | // Sha512 calculates hash(b) and returns the resulting 64 bytes. 24 | func Sha512(b []byte) []byte { 25 | data := sha512.Sum512(b) 26 | return data[:] 27 | } 28 | 29 | // Sha256HMAC - HMAC with SHA256 30 | func Sha256HMAC(b, key []byte) []byte { 31 | mac := hmac.New(sha256.New, key) 32 | mac.Write(b) 33 | return mac.Sum(nil) 34 | } 35 | 36 | // Sha512HMAC - HMAC with SHA512 37 | func Sha512HMAC(b, key []byte) []byte { 38 | mac := hmac.New(sha512.New, key) 39 | mac.Write(b) 40 | return mac.Sum(nil) 41 | } 42 | 43 | // Ripemd160 hashes with RIPEMD160 44 | func Ripemd160(b []byte) []byte { 45 | ripe := ripemd160.New() // nolint:gosec // required 46 | _, _ = ripe.Write(b[:]) 47 | return ripe.Sum(nil) 48 | } 49 | 50 | // Hash160 hashes with SHA256 and then hashes again with RIPEMD160. 51 | func Hash160(b []byte) []byte { 52 | hash := Sha256(b) 53 | return Ripemd160(hash[:]) 54 | } 55 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/listCertificates-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "totalCertificates": 1, 4 | "certificates": [ 5 | { 6 | "type": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0ZXN0LXR5cGU=", 7 | "serialNumber": "AAAAAAAAAAAAAAAAAAB0ZXN0LXNlcmlhbC1udW1iZXI=", 8 | "subject": "025ad43a22ac38d0bc1f8bacaabb323b5d634703b7a774c4268f6a09e4ddf79097", 9 | "certifier": "0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1", 10 | "revocationOutpoint": "aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d.0", 11 | "fields": { 12 | "name": "Alice", 13 | "email": "alice@example.com" 14 | }, 15 | "signature": "3045022100a6f09ee70382ab364f3f6b040aebb8fe7a51dbc3b4c99cfeb2f7756432162833022067349b91a6319345996faddf36d1b2f3a502e4ae002205f9d2db85474f9aed5a" 16 | } 17 | ] 18 | }, 19 | "wire": "0001fd0e010000000000000000000000000000000000000000000000746573742d747970650000000000000000000000000000746573742d73657269616c2d6e756d626572025ad43a22ac38d0bc1f8bacaabb323b5d634703b7a774c4268f6a09e4ddf790970294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d000205656d61696c11616c696365406578616d706c652e636f6d046e616d6505416c6963653045022100a6f09ee70382ab364f3f6b040aebb8fe7a51dbc3b4c99cfeb2f7756432162833022067349b91a6319345996faddf36d1b2f3a502e4ae002205f9d2db85474f9aed5a0000" 20 | } -------------------------------------------------------------------------------- /docs/examples/identity_client/README.md: -------------------------------------------------------------------------------- 1 | # Identity Client Example 2 | 3 | This example demonstrates how to use the `IdentityClient` from the BSV Go SDK to manage and resolve identity certificates. 4 | 5 | ## Features Demonstrated 6 | 7 | 1. **Creating an Identity Client** 8 | - Initialize with default or custom settings 9 | 10 | 2. **Publicly Revealing Attributes** 11 | - Selectively reveal identity attributes on the blockchain 12 | - Using both standard and simplified TypeScript-compatible APIs 13 | 14 | 3. **Identity Resolution** 15 | - Resolving identities by identity key 16 | - Resolving identities by specific attributes 17 | 18 | 4. **Direct Identity Certificate Parsing** 19 | - Converting a certificate directly to a displayable identity 20 | 21 | ## Running the Example 22 | 23 | ```bash 24 | cd docs/examples/identity_client 25 | go run main.go 26 | ``` 27 | 28 | ## Notes 29 | 30 | - This example demonstrates the API usage but won't successfully broadcast transactions unless you provide real certificate data. 31 | - In a real application, you would obtain certificates through `wallet.acquireCertificate()` or another mechanism. 32 | - The example uses mock data to show the structure and flow of using the Identity Client. 33 | 34 | ## Learn More 35 | 36 | For more detailed information about identity management and verification in the SDK, refer to: 37 | - [Identity Client Documentation](../../identity/README.md) 38 | - [Identity Concepts](../../concepts/IDENTITY.md) -------------------------------------------------------------------------------- /wallet/serializer/sign_action_result.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/bsv-blockchain/go-sdk/util" 7 | "github.com/bsv-blockchain/go-sdk/wallet" 8 | ) 9 | 10 | func SerializeSignActionResult(result *wallet.SignActionResult) ([]byte, error) { 11 | w := util.NewWriter() 12 | 13 | // Txid and tx 14 | w.WriteOptionalBytes(result.Txid[:], util.BytesOptionWithFlag, util.BytesOptionTxIdLen) 15 | w.WriteOptionalBytes(result.Tx, util.BytesOptionWithFlag) 16 | 17 | // SendWithResults 18 | if err := writeTxidSliceWithStatus(w, result.SendWithResults); err != nil { 19 | return nil, fmt.Errorf("error writing sendWith results: %w", err) 20 | } 21 | 22 | return w.Buf, nil 23 | } 24 | 25 | func DeserializeSignActionResult(data []byte) (*wallet.SignActionResult, error) { 26 | r := util.NewReaderHoldError(data) 27 | result := &wallet.SignActionResult{} 28 | 29 | // Txid and tx 30 | txidBytes := r.ReadOptionalBytes(util.BytesOptionWithFlag, util.BytesOptionTxIdLen) 31 | copy(result.Txid[:], txidBytes) 32 | result.Tx = r.ReadOptionalBytes(util.BytesOptionWithFlag) 33 | 34 | // SendWithResults 35 | results, err := readTxidSliceWithStatus(&r.Reader) 36 | if err != nil { 37 | return nil, fmt.Errorf("reading sendWith results: %w", err) 38 | } 39 | result.SendWithResults = results 40 | 41 | r.CheckComplete() 42 | if r.Err != nil { 43 | return nil, fmt.Errorf("error deserializing SignActionResult: %w", r.Err) 44 | } 45 | 46 | return result, nil 47 | } 48 | -------------------------------------------------------------------------------- /docs/examples/ecies_shared/ecies_shared.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | ecies "github.com/bsv-blockchain/go-sdk/compat/ecies" 7 | ec "github.com/bsv-blockchain/go-sdk/primitives/ec" 8 | ) 9 | 10 | // Example of using ECIES to encrypt and decrypt data between two users 11 | 12 | func main() { 13 | 14 | myPrivateKey, _ := ec.PrivateKeyFromWif("L211enC224G1kV8pyyq7bjVd9SxZebnRYEzzM3i7ZHCc1c5E7dQu") 15 | recipientPublicKey, _ := ec.PublicKeyFromString("03121a7afe56fc8e25bca4bb2c94f35eb67ebe5b84df2e149d65b9423ee65b8b4b") 16 | 17 | encryptedData, _ := ecies.EncryptShared("hello world", recipientPublicKey, myPrivateKey) 18 | 19 | fmt.Println(encryptedData) 20 | // Prints: 21 | // QklFMQO7zpX/GS4XpthCy6/hT38ZKsBGbn8JKMGHOY5ifmaoT+nbjXrzxPofyG94/QHgX8QZ3+a/DfQbTJ+Qvm1KtZWZISHww7MM5oRZybxHjtAa+Q== 22 | 23 | decryptedData, _ := ecies.DecryptShared(encryptedData, myPrivateKey, recipientPublicKey) 24 | fmt.Printf("decryptedData: %s\n", decryptedData) 25 | // Prints: 26 | // decryptedData: hello world 27 | } 28 | 29 | // // user 1 30 | // user1Pk, _ := ec.NewPrivateKey() 31 | 32 | // // user 2 33 | // user2Pk, _ := ec.PublicKeyFromString("03121a7afe56fc8e25bca4bb2c94f35eb67ebe5b84df2e149d65b9423ee65b8b4b") 34 | 35 | // priv, _, encryptedData, _ := ec.EncryptShared(user1Pk, user2Pk, []byte("this is a test")) 36 | 37 | // decryptedData, _ := ec.DecryptWithPrivateKey(priv, hex.EncodeToString(encryptedData)) 38 | 39 | // fmt.Printf("decryptedData: %s\n", decryptedData) 40 | // } 41 | -------------------------------------------------------------------------------- /docs/low-level/type_42/type_42.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | ec "github.com/bsv-blockchain/go-sdk/primitives/ec" 8 | hash "github.com/bsv-blockchain/go-sdk/primitives/hash" 9 | ) 10 | 11 | func main() { 12 | // Alice and Bob generate their private keys 13 | alicePrivKey, err := ec.NewPrivateKey() 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | alicePubKey := alicePrivKey.PubKey() 18 | 19 | bobPrivKey, err := ec.NewPrivateKey() 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | bobPubKey := bobPrivKey.PubKey() 24 | 25 | // Both parties agree on an invoice number to use 26 | invoiceNumber := "2-simple signing protocol-1" 27 | 28 | // Alice derives a child private key for signing 29 | aliceSigningChild, err := alicePrivKey.DeriveChild(bobPubKey, invoiceNumber) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | 34 | // Alice signs a message for Bob 35 | message := hash.Sha256([]byte("Hi Bob")) 36 | signature, err := aliceSigningChild.Sign(message) 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | 41 | // Bob derives Alice's correct signing public key from her master public key 42 | aliceSigningPub, err := alicePubKey.DeriveChild(bobPrivKey, invoiceNumber) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | 47 | // Now, Bob can privately verify Alice's signature 48 | verified := aliceSigningPub.Verify(message, signature) 49 | fmt.Println("Verified:", verified) 50 | // Output should be true if everything is correct 51 | } 52 | -------------------------------------------------------------------------------- /util/test_cert_util/test_certificate_utils.go: -------------------------------------------------------------------------------- 1 | package tcu 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/bsv-blockchain/go-sdk/auth/certificates" 7 | ec "github.com/bsv-blockchain/go-sdk/primitives/ec" 8 | "github.com/bsv-blockchain/go-sdk/wallet" 9 | "github.com/bsv-blockchain/go-sdk/wallet/testcertificates" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | const CertificateFieldName = "field1" 14 | const CertificateFieldValue = "test value" 15 | const CertificateTypeName testCertificateTypeName = "requested_type" 16 | 17 | type testCertificateTypeName string 18 | 19 | func (ct testCertificateTypeName) String() string { 20 | return string(ct) 21 | } 22 | 23 | func (ct testCertificateTypeName) ToType(t testing.TB) wallet.CertificateType { 24 | certType, err := wallet.CertificateTypeFromString(ct.String()) 25 | require.NoError(t, err, "invalid test setup: invalid certificate type") 26 | return certType 27 | } 28 | 29 | func CreateValidCertificate(t testing.TB, subject *ec.PrivateKey, certifier *ec.PrivateKey, verifierKey *ec.PublicKey) *certificates.VerifiableCertificate { 30 | subjectWallet := wallet.NewTestWallet(t, subject) 31 | 32 | certManager := testcertificates.NewManager(t, subjectWallet) 33 | 34 | verifiableCert := certManager.CertificateForTest().WithType(CertificateTypeName.String()). 35 | WithFieldValue(CertificateFieldName, CertificateFieldValue). 36 | IssueWithCertifier(certifier). 37 | ToVerifiableCertificate(verifierKey) 38 | 39 | return verifiableCert 40 | } 41 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/acquireCertificate-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "type": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0ZXN0LXR5cGU=", 4 | "certifier": "0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1", 5 | "acquisitionProtocol": "direct", 6 | "fields": { 7 | "email": "alice@example.com", 8 | "name": "Alice" 9 | }, 10 | "serialNumber": "AAAAAAAAAAAAAAAAAAB0ZXN0LXNlcmlhbC1udW1iZXI=", 11 | "revocationOutpoint": "aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d.0", 12 | "signature": "3045022100a6f09ee70382ab364f3f6b040aebb8fe7a51dbc3b4c99cfeb2f7756432162833022067349b91a6319345996faddf36d1b2f3a502e4ae002205f9d2db85474f9aed5a", 13 | "keyringRevealer": "025ad43a22ac38d0bc1f8bacaabb323b5d634703b7a774c4268f6a09e4ddf79097", 14 | "keyringForSubject": { 15 | "field1": "key1", 16 | "field2": "key2" 17 | }, 18 | "privileged": false 19 | }, 20 | "wire": "11000000000000000000000000000000000000000000000000746573742d747970650294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a10205656d61696c11616c696365406578616d706c652e636f6d046e616d6505416c69636500ff010000000000000000000000000000746573742d73657269616c2d6e756d626572aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d00473045022100a6f09ee70382ab364f3f6b040aebb8fe7a51dbc3b4c99cfeb2f7756432162833022067349b91a6319345996faddf36d1b2f3a502e4ae002205f9d2db85474f9aed5a025ad43a22ac38d0bc1f8bacaabb323b5d634703b7a774c4268f6a09e4ddf7909702066669656c64310391ecb5066669656c64320391ecb6" 21 | } -------------------------------------------------------------------------------- /script/interpreter/README.md: -------------------------------------------------------------------------------- 1 | interpreter 2 | ======== 3 | 4 | [![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) 5 | [![GoDoc](https://pkg.go.dev/badge/github.com/bsv-blockchain/go-sdk/script/interpreter?utm_source=godoc)](http://godoc.org/github.com/bsv-blockchain/go-sdk/script/interpreter) 6 | 7 | Package interpreter implements the an interpreter for the bitcoin transaction language. There is 8 | a comprehensive test suite. 9 | 10 | This package has intentionally been designed so it can be used as a standalone 11 | package for any projects needing to use or validate bitcoin transaction scripts. 12 | 13 | ## Bitcoin Scripts 14 | 15 | Bitcoin provides a stack-based, FORTH-like language for the scripts in 16 | the bitcoin transactions. This language is not turing complete 17 | although it is still fairly powerful. A description of the language 18 | can be found at https://wiki.bitcoinsv.io/index.php/Script 19 | 20 | ## Installation and Updating 21 | 22 | ```bash 23 | $ go get -u github.com/bsv-blockchain/go-sdk/script/interpreter 24 | ``` 25 | 26 | ## Examples 27 | 28 | * [Standard Pay-to-pubkey-hash Script](http://github.com/bsv-blockchain/go-sdk/script/interpreter#example-PayToAddrScript) 29 | Demonstrates creating a script which pays to a bitcoin address. It also 30 | prints the created script hex and uses the DisasmString function to display 31 | the disassembled script. 32 | 33 | ## License 34 | 35 | Package interpreter is licensed under the [copyfree](http://copyfree.org) ISC 36 | License. 37 | -------------------------------------------------------------------------------- /wallet/serializer/discover_by_attributes_test.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "github.com/bsv-blockchain/go-sdk/util" 5 | "github.com/bsv-blockchain/go-sdk/wallet" 6 | "github.com/stretchr/testify/require" 7 | "testing" 8 | ) 9 | 10 | func TestDiscoverByAttributesArgs(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | args *wallet.DiscoverByAttributesArgs 14 | }{{ 15 | name: "full args", 16 | args: &wallet.DiscoverByAttributesArgs{ 17 | Attributes: map[string]string{ 18 | "field1": "value1", 19 | "field2": "value2", 20 | }, 21 | Limit: util.Uint32Ptr(10), 22 | Offset: util.Uint32Ptr(5), 23 | SeekPermission: util.BoolPtr(true), 24 | }, 25 | }, { 26 | name: "minimal args", 27 | args: &wallet.DiscoverByAttributesArgs{ 28 | Attributes: map[string]string{ 29 | "field1": "value1", 30 | }, 31 | }, 32 | }, { 33 | name: "undefined limit/offset", 34 | args: &wallet.DiscoverByAttributesArgs{ 35 | Attributes: map[string]string{ 36 | "field1": "value1", 37 | }, 38 | SeekPermission: util.BoolPtr(false), 39 | }, 40 | }} 41 | 42 | for _, tt := range tests { 43 | t.Run(tt.name, func(t *testing.T) { 44 | // Test serialization 45 | data, err := SerializeDiscoverByAttributesArgs(tt.args) 46 | require.NoError(t, err) 47 | 48 | // Test deserialization 49 | got, err := DeserializeDiscoverByAttributesArgs(data) 50 | require.NoError(t, err) 51 | 52 | // Compare results 53 | require.Equal(t, tt.args, got) 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 4 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 8 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 9 | golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= 10 | golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= 11 | golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= 12 | golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= 13 | golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= 14 | golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 15 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 17 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 18 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 19 | -------------------------------------------------------------------------------- /script/errors.go: -------------------------------------------------------------------------------- 1 | package script 2 | 3 | import "github.com/pkg/errors" 4 | 5 | // Sentinel errors raised by data ops. 6 | var ( 7 | ErrDataTooBig = errors.New("data too big") 8 | ErrDataTooSmall = errors.New("not enough data") 9 | ErrPartTooBig = errors.New("part too big") 10 | ErrScriptIndexOutOfRange = errors.New("script index out of range") 11 | ) 12 | 13 | // Sentinel errors raised by addresses. 14 | var ( 15 | ErrInvalidAddressLength = errors.New("invalid address length") 16 | ErrUnsupportedAddress = errors.New("address not supported") 17 | ) 18 | 19 | // Sentinel errors raised by inscriptions. 20 | var ( 21 | ErrP2PKHInscriptionNotFound = errors.New("no P2PKH inscription found") 22 | ) 23 | 24 | // Sentinel errors raised through encoding. 25 | var ( 26 | ErrEncodingBadChar = errors.New("bad char") 27 | ErrEncodingTooLong = errors.New("too long") 28 | ErrEncodingInvalidVersion = errors.New("not version 0 of 6f") 29 | ErrEncodingInvalidChecksum = errors.New("invalid checksum") 30 | ErrEncodingChecksumFailed = errors.New("checksum failed") 31 | ErrTextNoBIP76 = errors.New("text did not match the bip276 format") 32 | ) 33 | 34 | // Sentinel errors raised by the package. 35 | var ( 36 | ErrInvalidPKLen = errors.New("invalid public key length") 37 | ErrInvalidOpCode = errors.New("invalid opcode data") 38 | ErrEmptyScript = errors.New("script is empty") 39 | ErrNotP2PKH = errors.New("not a P2PKH") 40 | ErrInvalidOpcodeType = errors.New("use AppendPushData for push data funcs") 41 | ) 42 | -------------------------------------------------------------------------------- /auth/brc104/auth_http_headers.go: -------------------------------------------------------------------------------- 1 | package brc104 2 | 3 | // BRC-104 HTTP header constants 4 | // These headers are used for BSV authentication over HTTP transport 5 | const ( 6 | // AuthHeaderPrefix is the common prefix for all BSV auth headers 7 | AuthHeaderPrefix = "x-bsv-auth" 8 | 9 | // HeaderVersion specifies the version of the auth protocol 10 | HeaderVersion = AuthHeaderPrefix + "-version" 11 | 12 | // HeaderMessageType specifies the type of auth message 13 | HeaderMessageType = AuthHeaderPrefix + "-message-type" 14 | 15 | // HeaderIdentityKey contains the sender's identity public key 16 | HeaderIdentityKey = AuthHeaderPrefix + "-identity-key" 17 | 18 | // HeaderNonce contains the sender's nonce 19 | HeaderNonce = AuthHeaderPrefix + "-nonce" 20 | 21 | // HeaderYourNonce contains the recipient's nonce (echoed back) 22 | HeaderYourNonce = AuthHeaderPrefix + "-your-nonce" 23 | 24 | // HeaderSignature contains the message signature 25 | HeaderSignature = AuthHeaderPrefix + "-signature" 26 | 27 | // HeaderRequestID contains the request ID for correlation 28 | HeaderRequestID = AuthHeaderPrefix + "-request-id" 29 | 30 | HeaderRequestedCertificates = AuthHeaderPrefix + "-requested-certificates" 31 | 32 | HeaderContentType = "content-type" 33 | 34 | HeaderAuthorization = "authorization" 35 | 36 | XBSVHeaderPrefix = "x-bsv" 37 | 38 | RequestIDLength = 32 39 | ) 40 | 41 | var NonXBSVIncludedRequestHeaders = []string{ 42 | HeaderContentType, 43 | HeaderAuthorization, 44 | } 45 | 46 | var NonXBSVIncludedResponseHeaders = []string{ 47 | HeaderAuthorization, 48 | } 49 | -------------------------------------------------------------------------------- /wallet/serializer/discover_by_identity_key.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "fmt" 5 | 6 | ec "github.com/bsv-blockchain/go-sdk/primitives/ec" 7 | "github.com/bsv-blockchain/go-sdk/util" 8 | "github.com/bsv-blockchain/go-sdk/wallet" 9 | ) 10 | 11 | func SerializeDiscoverByIdentityKeyArgs(args *wallet.DiscoverByIdentityKeyArgs) ([]byte, error) { 12 | w := util.NewWriter() 13 | 14 | // Write identity key (33 bytes) 15 | if args.IdentityKey == nil { 16 | return nil, fmt.Errorf("identityKey cannot be empty") 17 | } 18 | w.WriteBytes(args.IdentityKey.Compressed()) 19 | 20 | // Write limit, offset, seek permission 21 | w.WriteOptionalUint32(args.Limit) 22 | w.WriteOptionalUint32(args.Offset) 23 | w.WriteOptionalBool(args.SeekPermission) 24 | 25 | return w.Buf, nil 26 | } 27 | 28 | func DeserializeDiscoverByIdentityKeyArgs(data []byte) (*wallet.DiscoverByIdentityKeyArgs, error) { 29 | r := util.NewReaderHoldError(data) 30 | args := &wallet.DiscoverByIdentityKeyArgs{} 31 | 32 | // Read identity key (33 bytes) 33 | parsedIdentityKey, err := ec.PublicKeyFromBytes(r.ReadBytes(sizePubKey)) 34 | if err != nil { 35 | return nil, fmt.Errorf("error parsing identityKey: %w", err) 36 | } 37 | args.IdentityKey = parsedIdentityKey 38 | 39 | // Read limit 40 | args.Limit = r.ReadOptionalUint32() 41 | args.Offset = r.ReadOptionalUint32() 42 | args.SeekPermission = r.ReadOptionalBool() 43 | 44 | r.CheckComplete() 45 | if r.Err != nil { 46 | return nil, fmt.Errorf("error deserializing DiscoverByIdentityKey args: %w", r.Err) 47 | } 48 | 49 | return args, nil 50 | } 51 | -------------------------------------------------------------------------------- /wallet/serializer/relinquish_output.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/bsv-blockchain/go-sdk/util" 7 | "github.com/bsv-blockchain/go-sdk/wallet" 8 | ) 9 | 10 | func SerializeRelinquishOutputArgs(args *wallet.RelinquishOutputArgs) ([]byte, error) { 11 | w := util.NewWriter() 12 | 13 | // Write basket string with length prefix 14 | w.WriteString(args.Basket) 15 | 16 | // Write outpoint string with length prefix 17 | w.WriteBytes(encodeOutpoint(&args.Output)) 18 | 19 | return w.Buf, nil 20 | } 21 | 22 | func DeserializeRelinquishOutputArgs(data []byte) (*wallet.RelinquishOutputArgs, error) { 23 | r := util.NewReaderHoldError(data) 24 | args := &wallet.RelinquishOutputArgs{ 25 | Basket: r.ReadString(), 26 | } 27 | outpoint, err := decodeOutpoint(&r.Reader) 28 | r.CheckComplete() 29 | if r.Err != nil { 30 | return nil, fmt.Errorf("error reading relinquish output: %w", r.Err) 31 | } else if err != nil { 32 | return nil, fmt.Errorf("error decoding relinqush outpoint: %w", r.Err) 33 | } 34 | args.Output = *outpoint 35 | return args, nil 36 | } 37 | 38 | func SerializeRelinquishOutputResult(result *wallet.RelinquishOutputResult) ([]byte, error) { 39 | return nil, nil 40 | } 41 | 42 | func DeserializeRelinquishOutputResult(data []byte) (*wallet.RelinquishOutputResult, error) { 43 | if len(data) > 0 { 44 | return nil, fmt.Errorf("invalid result data length, expected 0, got %d", len(data)) 45 | } 46 | // Error in frame, empty data means success 47 | return &wallet.RelinquishOutputResult{ 48 | Relinquished: true, 49 | }, nil 50 | } 51 | -------------------------------------------------------------------------------- /wallet/substrates/wallet_wire_calls.go: -------------------------------------------------------------------------------- 1 | package substrates 2 | 3 | // Call represents the different types of wallet wire protocol operations. 4 | // Each call type corresponds to a specific wallet function that can be invoked remotely. 5 | type Call byte 6 | 7 | const ( 8 | CallCreateAction Call = 1 9 | CallSignAction Call = 2 10 | CallAbortAction Call = 3 11 | CallListActions Call = 4 12 | CallInternalizeAction Call = 5 13 | CallListOutputs Call = 6 14 | CallRelinquishOutput Call = 7 15 | CallGetPublicKey Call = 8 16 | CallRevealCounterpartyKeyLinkage Call = 9 17 | CallRevealSpecificKeyLinkage Call = 10 18 | CallEncrypt Call = 11 19 | CallDecrypt Call = 12 20 | CallCreateHMAC Call = 13 21 | CallVerifyHMAC Call = 14 22 | CallCreateSignature Call = 15 23 | CallVerifySignature Call = 16 24 | CallAcquireCertificate Call = 17 25 | CallListCertificates Call = 18 26 | CallProveCertificate Call = 19 27 | CallRelinquishCertificate Call = 20 28 | CallDiscoverByIdentityKey Call = 21 29 | CallDiscoverByAttributes Call = 22 30 | CallIsAuthenticated Call = 23 31 | CallWaitForAuthentication Call = 24 32 | CallGetHeight Call = 25 33 | CallGetHeaderForHeight Call = 26 34 | CallGetNetwork Call = 27 35 | CallGetVersion Call = 28 36 | ) 37 | -------------------------------------------------------------------------------- /overlay/lookup/facilitator.go: -------------------------------------------------------------------------------- 1 | package lookup 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "errors" 8 | "net/http" 9 | 10 | "github.com/bsv-blockchain/go-sdk/util" 11 | ) 12 | 13 | // Facilitator defines the interface for overlay lookup facilitators that can execute lookup queries 14 | type Facilitator interface { 15 | Lookup(ctx context.Context, url string, question *LookupQuestion) (*LookupAnswer, error) 16 | } 17 | 18 | // HTTPSOverlayLookupFacilitator implements the Facilitator interface using HTTPS requests 19 | type HTTPSOverlayLookupFacilitator struct { 20 | Client util.HTTPClient 21 | } 22 | 23 | // Lookup executes a lookup question against the specified URL and returns the answer 24 | func (f *HTTPSOverlayLookupFacilitator) Lookup(ctx context.Context, url string, question *LookupQuestion) (*LookupAnswer, error) { 25 | if q, err := json.Marshal(question); err != nil { 26 | return nil, err 27 | } else { 28 | req, err := http.NewRequestWithContext(ctx, "POST", url+"/lookup", bytes.NewBuffer(q)) 29 | if err != nil { 30 | return nil, err 31 | } 32 | req.Header.Set("Content-Type", "application/json") 33 | resp, err := f.Client.Do(req) 34 | if err != nil { 35 | return nil, err 36 | } 37 | defer resp.Body.Close() 38 | if resp.StatusCode != http.StatusOK { 39 | return nil, &util.HTTPError{ 40 | StatusCode: resp.StatusCode, 41 | Err: errors.New("lookup failed"), 42 | } 43 | } 44 | answer := &LookupAnswer{} 45 | if err := json.NewDecoder(resp.Body).Decode(&answer); err != nil { 46 | return nil, err 47 | } 48 | return answer, nil 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /auth/utils/create_nonce.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "encoding/base64" 7 | "fmt" 8 | 9 | "github.com/bsv-blockchain/go-sdk/wallet" 10 | ) 11 | 12 | // CreateNonce generates a cryptographic nonce derived from the wallet 13 | // The nonce consists of random data combined with an HMAC calculated with the wallet 14 | // Follows the same pattern as the TypeScript SDK's createNonce function 15 | func CreateNonce(ctx context.Context, w wallet.KeyOperations, counterparty wallet.Counterparty) (string, error) { 16 | // Generate 16 bytes of random data (matching TypeScript implementation) 17 | randomBytes := make([]byte, 16) 18 | if _, err := rand.Read(randomBytes); err != nil { 19 | return "", fmt.Errorf("failed to generate random bytes: %w", err) 20 | } 21 | 22 | // Create encryption arguments for the wallet's CreateHMAC function 23 | args := wallet.CreateHMACArgs{ 24 | EncryptionArgs: wallet.EncryptionArgs{ 25 | ProtocolID: wallet.Protocol{ 26 | SecurityLevel: wallet.SecurityLevelEveryApp, 27 | Protocol: "server hmac", 28 | }, 29 | KeyID: string(randomBytes), 30 | Counterparty: counterparty, 31 | }, 32 | Data: randomBytes, 33 | } 34 | 35 | // Create an HMAC for the random data using the wallet's key 36 | hmac, err := w.CreateHMAC(ctx, args, "") 37 | if err != nil { 38 | return "", fmt.Errorf("failed to create HMAC: %w", err) 39 | } 40 | 41 | // Combine the random data and the HMAC 42 | combined := append(randomBytes, hmac.HMAC[:]...) 43 | 44 | // Encode as base64 45 | nonce := base64.StdEncoding.EncodeToString(combined) 46 | return nonce, nil 47 | } 48 | -------------------------------------------------------------------------------- /docs/examples/broadcaster/broadcaster.md: -------------------------------------------------------------------------------- 1 | # Example: Building a Custom Transaction Broadcast Client 2 | 3 | This guide walks through the necessary steps for building a custom transaction broadcast client. 4 | 5 | ## Overview 6 | 7 | A transaction broadcast client is a crucial component in any Bitcoin SV application, allowing it to communicate with the Bitcoin SV network. Implementing a transaction broadcaster can be accomplished using the clearly defined Broadcast interface. 8 | 9 | ``` go 10 | package main 11 | 12 | import ( 13 | "github.com/bsv-blockchain/go-sdk/transaction" 14 | "github.com/bsv-blockchain/go-sdk/transaction/broadcaster" 15 | ) 16 | 17 | func main() { 18 | 19 | // Create a new transaction 20 | hexTx := "010000000100" 21 | tx, _ := transaction.NewTransactionFromHex(hexTx) 22 | 23 | broadcaster := &broadcaster.Arc{ 24 | ApiUrl: "https://arc.gorillapool.io", 25 | ApiKey: "", 26 | } 27 | 28 | // To use TAAL API 29 | // broadcaster := &broadcaster.Taal{ 30 | // ApiKey: "", 31 | // } 32 | 33 | // To use Whats on Chain API 34 | // broadcaster := &broadcaster.WhatsOnChain{ 35 | // ApiKey: "", 36 | // Network: broadcaster.WOCMainnet 37 | // } 38 | 39 | // Broadcast the transaction 40 | success, failure := tx.Broadcast(broadcaster) 41 | 42 | // Check for errors 43 | if failure != nil { 44 | panic(failure) 45 | } 46 | 47 | // Print the success message and transaction ID 48 | println(success.Message, success.Txid) 49 | } 50 | 51 | ``` 52 | -------------------------------------------------------------------------------- /auth/utils/verify_nonce.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "context" 5 | "encoding/base64" 6 | "errors" 7 | "fmt" 8 | 9 | "github.com/bsv-blockchain/go-sdk/wallet" 10 | ) 11 | 12 | // VerifyNonce verifies that a nonce was derived from the given wallet 13 | // This is the Go equivalent of the TypeScript SDK's verifyNonce function 14 | func VerifyNonce( 15 | ctx context.Context, 16 | nonce string, 17 | w wallet.KeyOperations, 18 | counterparty wallet.Counterparty, 19 | ) (bool, error) { 20 | // Convert nonce from base64 to binary 21 | nonceBytes, err := base64.StdEncoding.DecodeString(nonce) 22 | if err != nil { 23 | return false, fmt.Errorf("failed to decode nonce: %w", err) 24 | } 25 | 26 | // Check nonce format 27 | if len(nonceBytes) <= 16 { // Need at least 16 bytes data + some HMAC 28 | return false, errors.New("invalid nonce format: too short") 29 | } 30 | 31 | // Split nonce into data and hmac parts (first 16 bytes are data) 32 | data := nonceBytes[:16] 33 | hmac := nonceBytes[16:] 34 | 35 | // Create args for wallet VerifyHMAC 36 | args := wallet.VerifyHMACArgs{ 37 | EncryptionArgs: wallet.EncryptionArgs{ 38 | ProtocolID: wallet.Protocol{ 39 | SecurityLevel: wallet.SecurityLevelEveryApp, 40 | Protocol: "server hmac", // Match TS SDK's protocol ID 41 | }, 42 | KeyID: string(data), // Use data as key ID 43 | Counterparty: counterparty, 44 | }, 45 | Data: data, 46 | } 47 | copy(args.HMAC[:], hmac) 48 | 49 | // Verify the hmac 50 | result, err := w.VerifyHMAC(ctx, args, "") 51 | if err != nil { 52 | return false, fmt.Errorf("failed to verify HMAC: %w", err) 53 | } 54 | 55 | return result.Valid, nil 56 | } 57 | -------------------------------------------------------------------------------- /script/addressvalidation_test.go: -------------------------------------------------------------------------------- 1 | package script_test 2 | 3 | import ( 4 | "testing" 5 | 6 | script "github.com/bsv-blockchain/go-sdk/script" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestValidateAddress(t *testing.T) { 11 | t.Parallel() 12 | 13 | t.Run("mainnet P2PKH", func(t *testing.T) { 14 | ok, err := script.ValidateAddress("114ZWApV4EEU8frr7zygqQcB1V2BodGZuS") 15 | require.NoError(t, err) 16 | require.True(t, ok) 17 | }) 18 | 19 | t.Run("testnet P2PKH", func(t *testing.T) { 20 | ok, err := script.ValidateAddress("mfaWoDuTsFfiunLTqZx4fKpVsUctiDV9jk") 21 | require.NoError(t, err) 22 | require.True(t, ok) 23 | }) 24 | 25 | t.Run("BIP276", func(t *testing.T) { 26 | ok, err := script.ValidateAddress("bitcoin-script:0101522102e5b3f2970648b5592b7303367ab7d7d49e6e27dd80c7b5da18a22dac67a51a322103da6bf6a0c1a06ae7c4091542e0eaa29f2678e7957b78ba09cbe5a36241a4ad0452aeb245ccc7") 27 | require.NoError(t, err) 28 | require.True(t, ok) 29 | }) 30 | 31 | t.Run("empty address", func(t *testing.T) { 32 | ok, err := script.ValidateAddress("") 33 | require.Error(t, err) 34 | require.False(t, ok) 35 | }) 36 | 37 | t.Run("empty script", func(t *testing.T) { 38 | ok, err := script.ValidateAddress("bitcoin-script:") 39 | require.Error(t, err) 40 | require.False(t, ok) 41 | }) 42 | 43 | t.Run("invalid address", func(t *testing.T) { 44 | ok, err := script.ValidateAddress("invalid") 45 | require.Error(t, err) 46 | require.False(t, ok) 47 | }) 48 | 49 | t.Run("invalid script", func(t *testing.T) { 50 | ok, err := script.ValidateAddress("bitcoin-script:invalid") 51 | require.Error(t, err) 52 | require.False(t, ok) 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /wallet/serializer/discover_by_identity_key_test.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/bsv-blockchain/go-sdk/util" 7 | tu "github.com/bsv-blockchain/go-sdk/util/test_util" 8 | "github.com/bsv-blockchain/go-sdk/wallet" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestDiscoverByIdentityKeyArgs(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | args *wallet.DiscoverByIdentityKeyArgs 16 | }{{ 17 | name: "full args", 18 | args: &wallet.DiscoverByIdentityKeyArgs{ 19 | IdentityKey: tu.GetPKFromHex(t, "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), 20 | Limit: util.Uint32Ptr(10), 21 | Offset: util.Uint32Ptr(5), 22 | SeekPermission: util.BoolPtr(true), 23 | }, 24 | }, { 25 | name: "minimal args", 26 | args: &wallet.DiscoverByIdentityKeyArgs{ 27 | IdentityKey: tu.GetPKFromHex(t, "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"), 28 | }, 29 | }, { 30 | name: "undefined limit/offset", 31 | args: &wallet.DiscoverByIdentityKeyArgs{ 32 | IdentityKey: tu.GetPKFromHex(t, "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"), 33 | SeekPermission: util.BoolPtr(false), 34 | }, 35 | }} 36 | 37 | for _, tt := range tests { 38 | t.Run(tt.name, func(t *testing.T) { 39 | // Test serialization 40 | data, err := SerializeDiscoverByIdentityKeyArgs(tt.args) 41 | require.NoError(t, err) 42 | 43 | // Test deserialization 44 | got, err := DeserializeDiscoverByIdentityKeyArgs(data) 45 | require.NoError(t, err) 46 | 47 | // Compare results 48 | require.Equal(t, tt.args, got) 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/proveCertificate-simple-args.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "certificate": { 4 | "type": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0ZXN0LXR5cGU=", 5 | "serialNumber": "AAAAAAAAAAAAAAAAAAB0ZXN0LXNlcmlhbC1udW1iZXI=", 6 | "subject": "025ad43a22ac38d0bc1f8bacaabb323b5d634703b7a774c4268f6a09e4ddf79097", 7 | "certifier": "0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1", 8 | "revocationOutpoint": "aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d.0", 9 | "fields": { 10 | "email": "alice@example.com", 11 | "name": "Alice" 12 | }, 13 | "signature": "3045022100a6f09ee70382ab364f3f6b040aebb8fe7a51dbc3b4c99cfeb2f7756432162833022067349b91a6319345996faddf36d1b2f3a502e4ae002205f9d2db85474f9aed5a" 14 | }, 15 | "fieldsToReveal": [ 16 | "name" 17 | ], 18 | "verifier": "03b106dae20ae8fca0f4e8983d974c4b583054573eecdcdcfad261c035415ce1ee", 19 | "privileged": false, 20 | "privilegedReason": "prove-reason" 21 | }, 22 | "wire": "13000000000000000000000000000000000000000000000000746573742d74797065025ad43a22ac38d0bc1f8bacaabb323b5d634703b7a774c4268f6a09e4ddf790970000000000000000000000000000746573742d73657269616c2d6e756d6265720294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d00473045022100a6f09ee70382ab364f3f6b040aebb8fe7a51dbc3b4c99cfeb2f7756432162833022067349b91a6319345996faddf36d1b2f3a502e4ae002205f9d2db85474f9aed5a0205656d61696c11616c696365406578616d706c652e636f6d046e616d6505416c69636501046e616d6503b106dae20ae8fca0f4e8983d974c4b583054573eecdcdcfad261c035415ce1ee000c70726f76652d726561736f6e" 23 | } -------------------------------------------------------------------------------- /script/interpreter/debug/example_test.go: -------------------------------------------------------------------------------- 1 | package debug_test 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/bsv-blockchain/go-sdk/script" 9 | "github.com/bsv-blockchain/go-sdk/script/interpreter" 10 | "github.com/bsv-blockchain/go-sdk/script/interpreter/debug" 11 | ) 12 | 13 | func ExampleDebugger_AfterStep() { 14 | lockingScript, err := script.NewFromASM("777f726c64 OP_SWAP OP_CAT OP_SHA256 8376118fc0230e6054e782fb31ae52ebcfd551342d8d026c209997e0127b6f74 OP_EQUAL") 15 | if err != nil { 16 | fmt.Println(err) 17 | return 18 | } 19 | 20 | unlockingScript, err := script.NewFromASM(hex.EncodeToString([]byte("hello"))) 21 | if err != nil { 22 | fmt.Println(err) 23 | return 24 | } 25 | 26 | debugger := debug.NewDebugger() 27 | debugger.AttachAfterStep(func(state *interpreter.State) { 28 | frames := make([]string, len(state.DataStack)) 29 | for i, frame := range state.DataStack { 30 | frames[i] = hex.EncodeToString(frame) 31 | } 32 | fmt.Println(strings.Join(frames, "|")) 33 | }) 34 | 35 | if err := interpreter.NewEngine().Execute( 36 | interpreter.WithScripts(lockingScript, unlockingScript), 37 | interpreter.WithAfterGenesis(), 38 | interpreter.WithDebugger(debugger), 39 | ); err != nil { 40 | fmt.Println(err) 41 | } 42 | 43 | // Output: 44 | // 68656c6c6f 45 | // 68656c6c6f|777f726c64 46 | // 777f726c64|68656c6c6f 47 | // 777f726c6468656c6c6f 48 | // 8a0e597fd66749ca1a2f098f4ef706422c63a96dceef4abfd74517b10cd12f63 49 | // 8a0e597fd66749ca1a2f098f4ef706422c63a96dceef4abfd74517b10cd12f63|8376118fc0230e6054e782fb31ae52ebcfd551342d8d026c209997e0127b6f74 50 | // 51 | // false stack entry at end of script execution 52 | } 53 | -------------------------------------------------------------------------------- /primitives/ec/testdata/SymmetricKey.vectors.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "ciphertext": "1cf74FpvW0koFZk5e1VQcCtF7UdLj9mtN/L9loFlXwhf6w/06THwVirsvDShuT/KlOjO/HFALj8AcGLU1KRs4zNJDaX2wNebuPkH+qp5N/0cp3fZxgFzHJB3jBPDcdFi8O9WXIBLx9jUQ5KFQk0mZCB2k90VniInWuzqqOQAQQlBy2rgBWp4xg==", 4 | "key": "LIe9CoVXxDDDKt9F4j2lE+GP4oPcMElwyX+LVsuRLqw=", 5 | "plaintext": "5+w9tts+i14GDfPSEJwcaAfce7zVLC7wsRAMnCBqIczkqL08I05FZTl7n14H9hnPkS7HBm3EGWNDKCZ64ckCGg==" 6 | }, 7 | { 8 | "ciphertext": "IFh45HxwvK7wgIZr5UDxvUiEkvjsXVV6VIksaEQoTNCPleaRxE1CE1eZj5ZSPa/Mo2HXa2kvEmVAMslY12gMb7qHAHT2fSORB8TJKubKcjwGUrRxqOWvk24lv7QKhq3uhKkJxZSkPBZS6UM+xX+x7Mb53CoC8Z+7Ork50wGRAA415C+T8FIluA==", 9 | "key": "Di30+CTH8yKVJfXmbkRU6DOesD042IkjZCbFL1lnNqY=", 10 | "plaintext": "6pHqDrkIuGmWIpB1spu30PP848D04WlERSjrEZ/JD0jfdS814cOjs4MFkePT1IHeM4+qGFwAMk7HKgWShOKFDQ==" 11 | }, 12 | { 13 | "ciphertext": "JeUMCTX3hW7uH7Njfqjtjxd/8jB0Uj4eLLbLNBSMqF3XJmtq2oyX/WWS1po8cwn7jrcK0k8mVxHax/DctH6CIDMc0udBxWYLDyftvIYr448otWmn2IKQN4d3Bh2PKdiIQOo36DO2wOy+T2OJSmJ2XvAkenSZIckCdPIQVpeIi7Bt2ZpHmkObkg==", 14 | "key": "v7kFn4JdB3OVVjy8lk7UTvWe0vY5Qyzn64Q0EVoezlU=", 15 | "plaintext": "bSYHdJn15pcsaI8CNmfjKQ3ZvMg7zBaxuxBqyWBmCLdqj29bK54C26G1mx5e605hDrFpuJoNSDTECrk67ebffA==" 16 | }, 17 | { 18 | "ciphertext": "ktpzKolKsvtWrvLl0yMdGvh5ngd1hiaNcC1b5yuzo2DEKO/4S7gePO/CWOmW/dloHhzfbBQH9rKDFKK7xHHgqYRc", 19 | "key": "qIgnjD0FfGVMiWo107bP0oHsLA402lhC7AYUFIKY1KQ=", 20 | "plaintext": "A cat and a mouse." 21 | }, 22 | { 23 | "ciphertext": "vremTalPp+NxN/loEtLMB94tEymdFk2TfBoTWNYcf4sQqYSNkx2WPdJ4LxrIsGuIg9KMOt7FOcIpDb6rRVpP", 24 | "key": "K7E/bf3wp6hrVeW0V1KvFJS5JZMhyxwPHCIW6wKBTb0=", 25 | "plaintext": "üñîçø∂é" 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /primitives/ec/testdata/BRC42.private.vectors.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "senderPublicKey": "033f9160df035156f1c48e75eae99914fa1a1546bec19781e8eddb900200bff9d1", 4 | "recipientPrivateKey": "6a1751169c111b4667a6539ee1be6b7cd9f6e9c8fe011a5f2fe31e03a15e0ede", 5 | "invoiceNumber": "f3WCaUmnN9U=", 6 | "privateKey": "761656715bbfa172f8f9f58f5af95d9d0dfd69014cfdcacc9a245a10ff8893ef" 7 | }, 8 | { 9 | "senderPublicKey": "027775fa43959548497eb510541ac34b01d5ee9ea768de74244a4a25f7b60fae8d", 10 | "recipientPrivateKey": "cab2500e206f31bc18a8af9d6f44f0b9a208c32d5cca2b22acfe9d1a213b2f36", 11 | "invoiceNumber": "2Ska++APzEc=", 12 | "privateKey": "09f2b48bd75f4da6429ac70b5dce863d5ed2b350b6f2119af5626914bdb7c276" 13 | }, 14 | { 15 | "senderPublicKey": "0338d2e0d12ba645578b0955026ee7554889ae4c530bd7a3b6f688233d763e169f", 16 | "recipientPrivateKey": "7a66d0896f2c4c2c9ac55670c71a9bc1bdbdfb4e8786ee5137cea1d0a05b6f20", 17 | "invoiceNumber": "cN/yQ7+k7pg=", 18 | "privateKey": "7114cd9afd1eade02f76703cc976c241246a2f26f5c4b7a3a0150ecc745da9f0" 19 | }, 20 | { 21 | "senderPublicKey": "02830212a32a47e68b98d477000bde08cb916f4d44ef49d47ccd4918d9aaabe9c8", 22 | "recipientPrivateKey": "6e8c3da5f2fb0306a88d6bcd427cbfba0b9c7f4c930c43122a973d620ffa3036", 23 | "invoiceNumber": "m2/QAsmwaA4=", 24 | "privateKey": "f1d6fb05da1225feeddd1cf4100128afe09c3c1aadbffbd5c8bd10d329ef8f40" 25 | }, 26 | { 27 | "senderPublicKey": "03f20a7e71c4b276753969e8b7e8b67e2dbafc3958d66ecba98dedc60a6615336d", 28 | "recipientPrivateKey": "e9d174eff5708a0a41b32624f9b9cc97ef08f8931ed188ee58d5390cad2bf68e", 29 | "invoiceNumber": "jgpUIjWFlVQ=", 30 | "privateKey": "c5677c533f17c30f79a40744b18085632b262c0c13d87f3848c385f1389f79a6" 31 | } 32 | ] 33 | -------------------------------------------------------------------------------- /.github/workflows/sonar.yaml: -------------------------------------------------------------------------------- 1 | name: sonarcloud-analysis 2 | on: 3 | pull_request: 4 | branches: 5 | - "master" 6 | push: 7 | branches: 8 | - "master" 9 | 10 | permissions: 11 | contents: read 12 | pull-requests: read 13 | 14 | env: 15 | GO_VERSION: '1.24' 16 | GOLANGCI_LINT_VERSION: v2.1.6 17 | 18 | jobs: 19 | sonarcloud: 20 | name: SonarCloud 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v6 25 | with: 26 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 27 | 28 | - uses: actions/setup-go@v6 29 | with: 30 | go-version: ${{ env.GO_VERSION }} 31 | 32 | - name: Install test reporter 33 | run: go install github.com/ctrf-io/go-ctrf-json-reporter/cmd/go-ctrf-json-reporter@latest 34 | 35 | - name: Run Go tests 36 | run: go test -json -coverprofile=coverage.out ./... | go-ctrf-json-reporter -output ctrf-report.json 37 | continue-on-error: true 38 | 39 | - name: Publish Test Summary Results 40 | run: npx github-actions-ctrf ctrf-report.json 41 | 42 | # Re-run golangci separately without exiting on errors and generating a report for use in Sonar 43 | - name: golangci-lint 44 | uses: golangci/golangci-lint-action@v9 45 | with: 46 | version: ${{ env.GOLANGCI_LINT_VERSION }} 47 | args: --timeout=5m --issues-exit-code=0 --output.checkstyle.path=golangci-lint-report.xml 48 | 49 | - name: SonarCloud Scan 50 | uses: SonarSource/sonarqube-scan-action@v6.0.0 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 54 | -------------------------------------------------------------------------------- /primitives/ec/testdata/BRC42.public.vectors.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "senderPrivateKey": "583755110a8c059de5cd81b8a04e1be884c46083ade3f779c1e022f6f89da94c", 4 | "recipientPublicKey": "02c0c1e1a1f7d247827d1bcf399f0ef2deef7695c322fd91a01a91378f101b6ffc", 5 | "invoiceNumber": "IBioA4D/OaE=", 6 | "publicKey": "03c1bf5baadee39721ae8c9882b3cf324f0bf3b9eb3fc1b8af8089ca7a7c2e669f" 7 | }, 8 | { 9 | "senderPrivateKey": "2c378b43d887d72200639890c11d79e8f22728d032a5733ba3d7be623d1bb118", 10 | "recipientPublicKey": "039a9da906ecb8ced5c87971e9c2e7c921e66ad450fd4fc0a7d569fdb5bede8e0f", 11 | "invoiceNumber": "PWYuo9PDKvI=", 12 | "publicKey": "0398cdf4b56a3b2e106224ff3be5253afd5b72de735d647831be51c713c9077848" 13 | }, 14 | { 15 | "senderPrivateKey": "d5a5f70b373ce164998dff7ecd93260d7e80356d3d10abf928fb267f0a6c7be6", 16 | "recipientPublicKey": "02745623f4e5de046b6ab59ce837efa1a959a8f28286ce9154a4781ec033b85029", 17 | "invoiceNumber": "X9pnS+bByrM=", 18 | "publicKey": "0273eec9380c1a11c5a905e86c2d036e70cbefd8991d9a0cfca671f5e0bbea4a3c" 19 | }, 20 | { 21 | "senderPrivateKey": "46cd68165fd5d12d2d6519b02feb3f4d9c083109de1bfaa2b5c4836ba717523c", 22 | "recipientPublicKey": "031e18bb0bbd3162b886007c55214c3c952bb2ae6c33dd06f57d891a60976003b1", 23 | "invoiceNumber": "+ktmYRHv3uQ=", 24 | "publicKey": "034c5c6bf2e52e8de8b2eb75883090ed7d1db234270907f1b0d1c2de1ddee5005d" 25 | }, 26 | { 27 | "senderPrivateKey": "7c98b8abd7967485cfb7437f9c56dd1e48ceb21a4085b8cdeb2a647f62012db4", 28 | "recipientPublicKey": "03c8885f1e1ab4facd0f3272bb7a48b003d2e608e1619fb38b8be69336ab828f37", 29 | "invoiceNumber": "PPfDTTcl1ao=", 30 | "publicKey": "03304b41cfa726096ffd9d8907fe0835f888869eda9653bca34eb7bcab870d3779" 31 | } 32 | ] 33 | -------------------------------------------------------------------------------- /primitives/ecdsa/testdata/BRC42.private.vectors.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "senderPublicKey": "033f9160df035156f1c48e75eae99914fa1a1546bec19781e8eddb900200bff9d1", 4 | "recipientPrivateKey": "6a1751169c111b4667a6539ee1be6b7cd9f6e9c8fe011a5f2fe31e03a15e0ede", 5 | "invoiceNumber": "f3WCaUmnN9U=", 6 | "privateKey": "761656715bbfa172f8f9f58f5af95d9d0dfd69014cfdcacc9a245a10ff8893ef" 7 | }, 8 | { 9 | "senderPublicKey": "027775fa43959548497eb510541ac34b01d5ee9ea768de74244a4a25f7b60fae8d", 10 | "recipientPrivateKey": "cab2500e206f31bc18a8af9d6f44f0b9a208c32d5cca2b22acfe9d1a213b2f36", 11 | "invoiceNumber": "2Ska++APzEc=", 12 | "privateKey": "09f2b48bd75f4da6429ac70b5dce863d5ed2b350b6f2119af5626914bdb7c276" 13 | }, 14 | { 15 | "senderPublicKey": "0338d2e0d12ba645578b0955026ee7554889ae4c530bd7a3b6f688233d763e169f", 16 | "recipientPrivateKey": "7a66d0896f2c4c2c9ac55670c71a9bc1bdbdfb4e8786ee5137cea1d0a05b6f20", 17 | "invoiceNumber": "cN/yQ7+k7pg=", 18 | "privateKey": "7114cd9afd1eade02f76703cc976c241246a2f26f5c4b7a3a0150ecc745da9f0" 19 | }, 20 | { 21 | "senderPublicKey": "02830212a32a47e68b98d477000bde08cb916f4d44ef49d47ccd4918d9aaabe9c8", 22 | "recipientPrivateKey": "6e8c3da5f2fb0306a88d6bcd427cbfba0b9c7f4c930c43122a973d620ffa3036", 23 | "invoiceNumber": "m2/QAsmwaA4=", 24 | "privateKey": "f1d6fb05da1225feeddd1cf4100128afe09c3c1aadbffbd5c8bd10d329ef8f40" 25 | }, 26 | { 27 | "senderPublicKey": "03f20a7e71c4b276753969e8b7e8b67e2dbafc3958d66ecba98dedc60a6615336d", 28 | "recipientPrivateKey": "e9d174eff5708a0a41b32624f9b9cc97ef08f8931ed188ee58d5390cad2bf68e", 29 | "invoiceNumber": "jgpUIjWFlVQ=", 30 | "privateKey": "c5677c533f17c30f79a40744b18085632b262c0c13d87f3848c385f1389f79a6" 31 | } 32 | ] 33 | -------------------------------------------------------------------------------- /primitives/ecdsa/testdata/BRC42.public.vectors.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "senderPrivateKey": "583755110a8c059de5cd81b8a04e1be884c46083ade3f779c1e022f6f89da94c", 4 | "recipientPublicKey": "02c0c1e1a1f7d247827d1bcf399f0ef2deef7695c322fd91a01a91378f101b6ffc", 5 | "invoiceNumber": "IBioA4D/OaE=", 6 | "publicKey": "03c1bf5baadee39721ae8c9882b3cf324f0bf3b9eb3fc1b8af8089ca7a7c2e669f" 7 | }, 8 | { 9 | "senderPrivateKey": "2c378b43d887d72200639890c11d79e8f22728d032a5733ba3d7be623d1bb118", 10 | "recipientPublicKey": "039a9da906ecb8ced5c87971e9c2e7c921e66ad450fd4fc0a7d569fdb5bede8e0f", 11 | "invoiceNumber": "PWYuo9PDKvI=", 12 | "publicKey": "0398cdf4b56a3b2e106224ff3be5253afd5b72de735d647831be51c713c9077848" 13 | }, 14 | { 15 | "senderPrivateKey": "d5a5f70b373ce164998dff7ecd93260d7e80356d3d10abf928fb267f0a6c7be6", 16 | "recipientPublicKey": "02745623f4e5de046b6ab59ce837efa1a959a8f28286ce9154a4781ec033b85029", 17 | "invoiceNumber": "X9pnS+bByrM=", 18 | "publicKey": "0273eec9380c1a11c5a905e86c2d036e70cbefd8991d9a0cfca671f5e0bbea4a3c" 19 | }, 20 | { 21 | "senderPrivateKey": "46cd68165fd5d12d2d6519b02feb3f4d9c083109de1bfaa2b5c4836ba717523c", 22 | "recipientPublicKey": "031e18bb0bbd3162b886007c55214c3c952bb2ae6c33dd06f57d891a60976003b1", 23 | "invoiceNumber": "+ktmYRHv3uQ=", 24 | "publicKey": "034c5c6bf2e52e8de8b2eb75883090ed7d1db234270907f1b0d1c2de1ddee5005d" 25 | }, 26 | { 27 | "senderPrivateKey": "7c98b8abd7967485cfb7437f9c56dd1e48ceb21a4085b8cdeb2a647f62012db4", 28 | "recipientPublicKey": "03c8885f1e1ab4facd0f3272bb7a48b003d2e608e1619fb38b8be69336ab828f37", 29 | "invoiceNumber": "PPfDTTcl1ao=", 30 | "publicKey": "03304b41cfa726096ffd9d8907fe0835f888869eda9653bca34eb7bcab870d3779" 31 | } 32 | ] 33 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/listCertificates-full-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "totalCertificates": 1, 4 | "certificates": [ 5 | { 6 | "type": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0ZXN0LXR5cGU=", 7 | "serialNumber": "AAAAAAAAAAAAAAAAAAB0ZXN0LXNlcmlhbC1udW1iZXI=", 8 | "subject": "025ad43a22ac38d0bc1f8bacaabb323b5d634703b7a774c4268f6a09e4ddf79097", 9 | "certifier": "0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1", 10 | "revocationOutpoint": "aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d.0", 11 | "fields": { 12 | "name": "Alice", 13 | "email": "alice@example.com" 14 | }, 15 | "signature": "3045022100a6f09ee70382ab364f3f6b040aebb8fe7a51dbc3b4c99cfeb2f7756432162833022067349b91a6319345996faddf36d1b2f3a502e4ae002205f9d2db85474f9aed5a", 16 | "keyring": { 17 | "field1": "a2V5MQ==", 18 | "field2": "a2V5Mg==" 19 | }, 20 | "verifier": "03b106dae20ae8fca0f4e8983d974c4b583054573eecdcdcfad261c035415ce1ee" 21 | } 22 | ] 23 | }, 24 | "wire": "0001fd0e010000000000000000000000000000000000000000000000746573742d747970650000000000000000000000000000746573742d73657269616c2d6e756d626572025ad43a22ac38d0bc1f8bacaabb323b5d634703b7a774c4268f6a09e4ddf790970294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d000205656d61696c11616c696365406578616d706c652e636f6d046e616d6505416c6963653045022100a6f09ee70382ab364f3f6b040aebb8fe7a51dbc3b4c99cfeb2f7756432162833022067349b91a6319345996faddf36d1b2f3a502e4ae002205f9d2db85474f9aed5a0102066669656c6431046b657931066669656c6432046b6579322103b106dae20ae8fca0f4e8983d974c4b583054573eecdcdcfad261c035415ce1ee" 25 | } -------------------------------------------------------------------------------- /transaction/output_test.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | const outputHexStr = "8a08ac4a000000001976a9148bf10d323ac757268eb715e613cb8e8e1d1793aa88ac00000000" 11 | 12 | func TestNewOutputFromBytes(t *testing.T) { 13 | t.Parallel() 14 | 15 | t.Run("invalid output, too short", func(t *testing.T) { 16 | o, s, err := newOutputFromBytes([]byte("")) 17 | require.Error(t, err) 18 | require.Nil(t, o) 19 | require.Equal(t, 0, s) 20 | }) 21 | 22 | t.Run("invalid output, too short + script", func(t *testing.T) { 23 | o, s, err := newOutputFromBytes([]byte("0000000000000")) 24 | require.Error(t, err) 25 | require.Nil(t, o) 26 | require.Equal(t, 0, s) 27 | }) 28 | 29 | t.Run("valid output", func(t *testing.T) { 30 | bytes, err := hex.DecodeString(outputHexStr) 31 | require.NoError(t, err) 32 | 33 | var o *TransactionOutput 34 | var s int 35 | o, s, err = newOutputFromBytes(bytes) 36 | require.NoError(t, err) 37 | require.NotNil(t, o) 38 | 39 | require.Equal(t, 34, s) 40 | require.Equal(t, uint64(1252788362), o.Satoshis) 41 | require.Len(t, *o.LockingScript, 25) 42 | require.Equal(t, "76a9148bf10d323ac757268eb715e613cb8e8e1d1793aa88ac", o.LockingScriptHex()) 43 | }) 44 | } 45 | 46 | func TestOutput_String(t *testing.T) { 47 | t.Run("compare string output", func(t *testing.T) { 48 | 49 | bytes, err := hex.DecodeString(outputHexStr) 50 | require.NoError(t, err) 51 | 52 | var o *TransactionOutput 53 | o, _, err = newOutputFromBytes(bytes) 54 | require.NoError(t, err) 55 | require.NotNil(t, o) 56 | 57 | require.Equal(t, "value: 1252788362\nscriptLen: 25\nscript: 76a9148bf10d323ac757268eb715e613cb8e8e1d1793aa88ac\n", o.String()) 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /primitives/aesgcm/ghash.go: -------------------------------------------------------------------------------- 1 | package primitives 2 | 3 | // r is a constant used in the multiplication, specific to AES's GF(2^128) field. 4 | var r = []byte{0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} 5 | 6 | // Ghash performs GHASH on the provided input using the provided key 7 | func Ghash(input, hashSubKey []byte) []byte { 8 | result := make([]byte, 16) 9 | 10 | for i := 0; i < len(input); i += 16 { 11 | block := input[i:min(i+16, len(input))] 12 | result = multiply(exclusiveOR(result, block), hashSubKey) 13 | } 14 | 15 | return result 16 | } 17 | 18 | // Min is a helper function to find the minimum of two integers. 19 | func min(a, b int) int { 20 | if a < b { 21 | return a 22 | } 23 | return b 24 | } 25 | 26 | func exclusiveOR(blockA, blockB []byte) []byte { 27 | result := make([]byte, len(blockA)) 28 | for i := range blockA { 29 | result[i] = blockA[i] ^ blockB[i] 30 | } 31 | return result 32 | } 33 | 34 | func rightShift(block []byte) []byte { 35 | carry := byte(0) 36 | for i := 0; i < len(block); i++ { 37 | oldCarry := carry 38 | carry = block[i] & 0x01 39 | block[i] >>= 1 40 | if oldCarry != 0 { 41 | block[i] |= 0x80 42 | } 43 | } 44 | return block 45 | } 46 | 47 | func checkBit(block []byte, index int, bit int) bool { 48 | return (block[index]>>bit)&1 == 1 49 | } 50 | 51 | func multiply(block0, block1 []byte) []byte { 52 | v := make([]byte, len(block1)) 53 | copy(v, block1) 54 | z := make([]byte, 16) 55 | 56 | for i := 0; i < 16; i++ { 57 | for j := 7; j >= 0; j-- { 58 | if checkBit(block0, i, j) { 59 | z = exclusiveOR(z, v) 60 | } 61 | if checkBit(v, 15, 0) { 62 | v = exclusiveOR(rightShift(v), r) 63 | } else { 64 | v = rightShift(v) 65 | } 66 | } 67 | } 68 | return z 69 | } 70 | -------------------------------------------------------------------------------- /util/big.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/rand" 5 | "math/big" 6 | ) 7 | 8 | // Umod returns the unsigned modulus of x and y. 9 | // It ensures the result is always non-negative. 10 | func Umod(x *big.Int, y *big.Int) *big.Int { 11 | // Ensure divisor y is not zero 12 | if y.Sign() == 0 { 13 | panic("division by zero") 14 | } 15 | 16 | // If the dividend x is zero, the modulus is zero 17 | if x.Sign() == 0 { 18 | return big.NewInt(0) 19 | } 20 | 21 | mod := new(big.Int) 22 | 23 | // Handle cases where x is negative and y is positive 24 | if x.Sign() < 0 && y.Sign() > 0 { 25 | mod.Neg(x) 26 | mod.Mod(mod, y) 27 | mod.Neg(mod) 28 | if mod.Sign() < 0 { 29 | mod.Add(mod, y) 30 | } 31 | } else if x.Sign() > 0 && y.Sign() < 0 { 32 | // Handle cases where x is positive and y is negative 33 | mod.Mod(x, new(big.Int).Neg(y)) 34 | } else if x.Sign() < 0 && y.Sign() < 0 { 35 | // Handle cases where both x and y are negative 36 | mod.Neg(x) 37 | mod.Mod(mod, new(big.Int).Neg(y)) 38 | mod.Neg(mod) 39 | if mod.Sign() < 0 { 40 | mod.Sub(mod, y) 41 | } 42 | } else { 43 | // Both numbers are positive 44 | mod.Mod(x, y) 45 | } 46 | 47 | // Ensure the result is non-negative 48 | if mod.Sign() < 0 { 49 | mod.Add(mod, new(big.Int).Abs(y)) 50 | } 51 | 52 | return mod 53 | } 54 | 55 | func NewRandomBigInt(byteLen int) *big.Int { 56 | b := make([]byte, byteLen) 57 | maxRetries := 10 58 | // gracefully handle errors in low entropy environments 59 | // or extreme resource constraints, embedded systems 60 | for retries := 0; retries < maxRetries; retries++ { 61 | _, err := rand.Read(b) 62 | if err == nil { 63 | return new(big.Int).SetBytes(b) 64 | } 65 | } 66 | // Failure here indicates a critical error 67 | panic("failed to generate random big.Int") 68 | } 69 | -------------------------------------------------------------------------------- /script/interpreter/engine.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013-2018 The btcsuite developers 2 | // Copyright (c) 2015-2018 The Decred developers 3 | // Use of this source code is governed by an ISC 4 | // license that can be found in the LICENSE file. 5 | 6 | package interpreter 7 | 8 | // Engine is the virtual machine that executes scripts. 9 | type Engine interface { 10 | Execute(opts ...ExecutionOptionFunc) error 11 | } 12 | 13 | type engine struct{} 14 | 15 | // NewEngine returns a new script engine for the provided locking script 16 | // (of a previous transaction out), transaction, and input index. The 17 | // flags modify the behavior of the script engine according to the 18 | // description provided by each flag. 19 | func NewEngine() Engine { 20 | return &engine{} 21 | } 22 | 23 | // Execute will execute all scripts in the script engine and return either nil 24 | // for successful validation or an error if one occurred. 25 | // 26 | // Execute with tx example: 27 | // 28 | // if err := engine.Execute( 29 | // interpreter.WithTx(tx, inputIdx, previousOutput), 30 | // interpreter.WithAfterGenesis(), 31 | // interpreter.WithForkID(), 32 | // ); err != nil { 33 | // // handle err 34 | // } 35 | // 36 | // Execute with scripts example: 37 | // 38 | // if err := engine.Execute( 39 | // interpreter.WithScripts(lockingScript, unlockingScript), 40 | // interpreter.WithAfterGenesis(), 41 | // interpreter.WithForkID(), 42 | // }); err != nil { 43 | // // handle err 44 | // } 45 | func (e *engine) Execute(oo ...ExecutionOptionFunc) error { 46 | opts := &execOpts{} 47 | for _, o := range oo { 48 | o(opts) 49 | } 50 | 51 | t, err := createThread(opts) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | if err := t.execute(); err != nil { 57 | t.afterError(err) 58 | return err 59 | } 60 | 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /compat/bsm/sign.go: -------------------------------------------------------------------------------- 1 | package compat 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "errors" 7 | 8 | ec "github.com/bsv-blockchain/go-sdk/primitives/ec" 9 | crypto "github.com/bsv-blockchain/go-sdk/primitives/hash" 10 | "github.com/bsv-blockchain/go-sdk/util" 11 | ) 12 | 13 | const hBSV = "Bitcoin Signed Message:\n" 14 | 15 | // SignMessage signs a string with the provided PrivateKey using Bitcoin Signed Message encoding 16 | // sigRefCompressedKey bool determines whether the signature will reference a compressed or uncompresed key 17 | // Spec: https://github.com/bitcoin/bitcoin/pull/524 18 | func SignMessage(privateKey *ec.PrivateKey, message []byte) ([]byte, error) { 19 | return SignMessageWithCompression(privateKey, message, true) 20 | } 21 | 22 | func SignMessageWithCompression(privateKey *ec.PrivateKey, message []byte, sigRefCompressedKey bool) ([]byte, error) { 23 | if privateKey == nil { 24 | return nil, errors.New("private key is required") 25 | } 26 | 27 | b := new(bytes.Buffer) 28 | 29 | varInt := util.VarInt(len(hBSV)) 30 | b.Write(varInt.Bytes()) 31 | 32 | // append the hBsv to buff 33 | b.WriteString(hBSV) 34 | 35 | varInt = util.VarInt(len(message)) 36 | b.Write(varInt.Bytes()) 37 | 38 | // append the data to buff 39 | b.Write(message) 40 | 41 | // Create the hash 42 | messageHash := crypto.Sha256d(b.Bytes()) 43 | 44 | // Sign 45 | return ec.SignCompact(ec.S256(), privateKey, messageHash, sigRefCompressedKey) 46 | } 47 | 48 | // SignMessageString signs the message and returns the signature as a base64-encoded string 49 | func SignMessageString(privateKey *ec.PrivateKey, message []byte) (string, error) { 50 | sigBytes, err := SignMessageWithCompression(privateKey, message, true) 51 | if err != nil { 52 | return "", err 53 | } 54 | 55 | return base64.StdEncoding.EncodeToString(sigBytes), nil 56 | } 57 | -------------------------------------------------------------------------------- /compat/base58/alphabet.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | // AUTOGENERATED by genalphabet.go; do not edit. 6 | 7 | package compat 8 | 9 | const ( 10 | // alphabet is the modified base58 alphabet used by Bitcoin. 11 | alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" 12 | 13 | alphabetIdx0 = '1' 14 | ) 15 | 16 | var b58 = [256]byte{ 17 | 255, 255, 255, 255, 255, 255, 255, 255, 18 | 255, 255, 255, 255, 255, 255, 255, 255, 19 | 255, 255, 255, 255, 255, 255, 255, 255, 20 | 255, 255, 255, 255, 255, 255, 255, 255, 21 | 255, 255, 255, 255, 255, 255, 255, 255, 22 | 255, 255, 255, 255, 255, 255, 255, 255, 23 | 255, 0, 1, 2, 3, 4, 5, 6, 24 | 7, 8, 255, 255, 255, 255, 255, 255, 25 | 255, 9, 10, 11, 12, 13, 14, 15, 26 | 16, 255, 17, 18, 19, 20, 21, 255, 27 | 22, 23, 24, 25, 26, 27, 28, 29, 28 | 30, 31, 32, 255, 255, 255, 255, 255, 29 | 255, 33, 34, 35, 36, 37, 38, 39, 30 | 40, 41, 42, 43, 255, 44, 45, 46, 31 | 47, 48, 49, 50, 51, 52, 53, 54, 32 | 55, 56, 57, 255, 255, 255, 255, 255, 33 | 255, 255, 255, 255, 255, 255, 255, 255, 34 | 255, 255, 255, 255, 255, 255, 255, 255, 35 | 255, 255, 255, 255, 255, 255, 255, 255, 36 | 255, 255, 255, 255, 255, 255, 255, 255, 37 | 255, 255, 255, 255, 255, 255, 255, 255, 38 | 255, 255, 255, 255, 255, 255, 255, 255, 39 | 255, 255, 255, 255, 255, 255, 255, 255, 40 | 255, 255, 255, 255, 255, 255, 255, 255, 41 | 255, 255, 255, 255, 255, 255, 255, 255, 42 | 255, 255, 255, 255, 255, 255, 255, 255, 43 | 255, 255, 255, 255, 255, 255, 255, 255, 44 | 255, 255, 255, 255, 255, 255, 255, 255, 45 | 255, 255, 255, 255, 255, 255, 255, 255, 46 | 255, 255, 255, 255, 255, 255, 255, 255, 47 | 255, 255, 255, 255, 255, 255, 255, 255, 48 | 255, 255, 255, 255, 255, 255, 255, 255, 49 | } 50 | -------------------------------------------------------------------------------- /docs/examples/set_source_tx_output/set_source_tx_output.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | ec "github.com/bsv-blockchain/go-sdk/primitives/ec" 7 | script "github.com/bsv-blockchain/go-sdk/script" 8 | "github.com/bsv-blockchain/go-sdk/transaction" 9 | "github.com/bsv-blockchain/go-sdk/transaction/template/p2pkh" 10 | ) 11 | 12 | // Example: Setting source transaction outputs on inputs when you don't have full BEEF 13 | // Problem: You have UTXOs from an API, but not the full source transactions. 14 | // Solution: For each input, look up the previous output (satoshis + locking script) 15 | // 16 | // and set it via SetSourceTxOutput. This enables sighash calculation and signing. 17 | func main() { 18 | // Create a transaction 19 | tx := transaction.NewTransaction() 20 | 21 | // Add an input from an outpoint (no script/satoshis provided here) 22 | if err := tx.AddInputFrom("45be95d2f2c64e99518ffbbce03fb15a7758f20ee5eecf0df07938d977add71d", 0, "", 0, nil); err != nil { 23 | log.Fatal(err) 24 | } 25 | 26 | // Fetch the source output details from your data provider 27 | // For the example, we build them locally 28 | lockingScript, err := script.NewFromHex("76a914c7c6987b6e2345a6b138e3384141520a0fbc18c588ac") 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | // Attach source output to input 0 33 | tx.Inputs[0].SetSourceTxOutput(&transaction.TransactionOutput{ 34 | Satoshis: 15564838601, 35 | LockingScript: lockingScript, 36 | }) 37 | 38 | // Sign input 0 with a private key 39 | priv, err := ec.PrivateKeyFromWif("cNGwGSc7KRrTmdLUZ54fiSXWbhLNDc2Eg5zNucgQxyQCzuQ5YRDq") 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | unlocker, err := p2pkh.Unlock(priv, nil) 44 | if err != nil { 45 | log.Fatal(err) 46 | } 47 | us, err := unlocker.Sign(tx, 0) 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | tx.Inputs[0].UnlockingScript = us 52 | } 53 | -------------------------------------------------------------------------------- /compat/base58/base58.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013-2015 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package compat 6 | 7 | import ( 8 | "errors" 9 | "math/big" 10 | ) 11 | 12 | //go:generate go run genalphabet.go 13 | 14 | var bigRadix = big.NewInt(58) 15 | var bigZero = big.NewInt(0) 16 | 17 | // Decode decodes a modified base58 string to a byte slice. 18 | func Decode(b string) ([]byte, error) { 19 | answer := big.NewInt(0) 20 | j := big.NewInt(1) 21 | 22 | scratch := new(big.Int) 23 | for i := len(b) - 1; i >= 0; i-- { 24 | tmp := b58[b[i]] 25 | if tmp == 255 { 26 | return []byte(""), errors.New("bad character in encoding") 27 | } 28 | scratch.SetInt64(int64(tmp)) 29 | scratch.Mul(j, scratch) 30 | answer.Add(answer, scratch) 31 | j.Mul(j, bigRadix) 32 | } 33 | 34 | tmpval := answer.Bytes() 35 | 36 | var numZeros int 37 | for numZeros = 0; numZeros < len(b); numZeros++ { 38 | if b[numZeros] != alphabetIdx0 { 39 | break 40 | } 41 | } 42 | flen := numZeros + len(tmpval) 43 | val := make([]byte, flen) 44 | copy(val[numZeros:], tmpval) 45 | 46 | return val, nil 47 | } 48 | 49 | // Encode encodes a byte slice to a modified base58 string. 50 | func Encode(b []byte) string { 51 | x := new(big.Int) 52 | x.SetBytes(b) 53 | 54 | answer := make([]byte, 0, len(b)*136/100) 55 | for x.Cmp(bigZero) > 0 { 56 | mod := new(big.Int) 57 | x.DivMod(x, bigRadix, mod) 58 | answer = append(answer, alphabet[mod.Int64()]) 59 | } 60 | 61 | // leading zero bytes 62 | for _, i := range b { 63 | if i != 0 { 64 | break 65 | } 66 | answer = append(answer, alphabetIdx0) 67 | } 68 | 69 | // reverse 70 | alen := len(answer) 71 | for i := 0; i < alen/2; i++ { 72 | answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i] 73 | } 74 | 75 | return string(answer) 76 | } 77 | -------------------------------------------------------------------------------- /docs/examples/create_tx_with_inscription/create_tx_with_inscription.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "mime" 7 | "os" 8 | 9 | ec "github.com/bsv-blockchain/go-sdk/primitives/ec" 10 | script "github.com/bsv-blockchain/go-sdk/script" 11 | "github.com/bsv-blockchain/go-sdk/transaction" 12 | "github.com/bsv-blockchain/go-sdk/transaction/template/p2pkh" 13 | ) 14 | 15 | func main() { 16 | priv, _ := ec.PrivateKeyFromWif("KznpA63DPFrmHecASyL6sFmcRgrNT9oM8Ebso8mwq1dfJF3ZgZ3V") 17 | 18 | unlocker, err := p2pkh.Unlock(priv, nil) // get public key bytes and address 19 | if err != nil { 20 | fmt.Println(err) 21 | return 22 | } 23 | 24 | tx := transaction.NewTransaction() 25 | _ = tx.AddInputFrom( 26 | "39e5954ee335fdb5a1368ab9e851a954ed513f73f6e8e85eff5e31adbb5837e7", 27 | 0, 28 | "76a9144bca0c466925b875875a8e1355698bdcc0b2d45d88ac", 29 | 500, 30 | unlocker, 31 | ) 32 | 33 | // Read the image file 34 | data, err := os.ReadFile("1SatLogoLight.png") 35 | if err != nil { 36 | fmt.Println(err) 37 | return 38 | } 39 | 40 | // Get the content type of the image 41 | contentType := mime.TypeByExtension(".png") 42 | add, err := script.NewAddressFromPublicKey(priv.PubKey(), true) 43 | if err != nil { 44 | fmt.Println(err) 45 | return 46 | } 47 | s, _ := p2pkh.Lock(add) 48 | err = tx.Inscribe(&script.InscriptionArgs{ 49 | LockingScript: s, 50 | Data: data, 51 | ContentType: contentType, 52 | }) 53 | if err != nil { 54 | log.Fatal(err) 55 | } 56 | 57 | changeAdd, _ := script.NewAddressFromString("17ujiveRLkf2JQiGR8Sjtwb37evX7vG3WG") 58 | 59 | changeScript, _ := p2pkh.Lock(changeAdd) 60 | tx.AddOutput(&transaction.TransactionOutput{ 61 | LockingScript: changeScript, 62 | Change: true, 63 | }) 64 | 65 | err = tx.Sign() 66 | if err != nil { 67 | log.Fatal(err.Error()) 68 | } 69 | 70 | fmt.Println(tx.String()) 71 | } 72 | -------------------------------------------------------------------------------- /wallet/serializer/get_height_test.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "github.com/bsv-blockchain/go-sdk/wallet" 5 | "github.com/stretchr/testify/require" 6 | "testing" 7 | ) 8 | 9 | func TestGetHeightResult(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | height uint32 13 | }{ 14 | { 15 | name: "zero height", 16 | height: 0, 17 | }, 18 | { 19 | name: "small height", 20 | height: 123, 21 | }, 22 | { 23 | name: "large height", 24 | height: 123456789, 25 | }, 26 | } 27 | 28 | for _, tt := range tests { 29 | t.Run(tt.name, func(t *testing.T) { 30 | // Test serialization 31 | result := &wallet.GetHeightResult{Height: tt.height} 32 | data, err := SerializeGetHeightResult(result) 33 | require.NoError(t, err, "serializing GetHeightResult should not error") 34 | 35 | // Test deserialization 36 | got, err := DeserializeGetHeightResult(data) 37 | require.NoError(t, err, "deserializing GetHeightResult should not error") 38 | require.Equal(t, result, got, "deserialized result should match original result") 39 | }) 40 | } 41 | } 42 | 43 | func TestDeserializeGetHeightResultErrors(t *testing.T) { 44 | tests := []struct { 45 | name string 46 | data []byte 47 | wantErr string 48 | }{ 49 | { 50 | name: "empty data", 51 | data: []byte{}, 52 | wantErr: "error reading height", 53 | }, 54 | { 55 | name: "invalid varint", 56 | data: []byte{0xFF, 0x80}, // Invalid varint (incomplete varint) 57 | wantErr: "error reading height", 58 | }, 59 | } 60 | 61 | for _, tt := range tests { 62 | t.Run(tt.name, func(t *testing.T) { 63 | _, err := DeserializeGetHeightResult(tt.data) 64 | require.Error(t, err, "deserializing invalid data should produce an error") 65 | require.Contains(t, err.Error(), tt.wantErr, "error message should contain expected substring") 66 | }) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /wallet/serializer/discover_certificates_result.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/bsv-blockchain/go-sdk/util" 7 | "github.com/bsv-blockchain/go-sdk/wallet" 8 | ) 9 | 10 | func SerializeDiscoverCertificatesResult(result *wallet.DiscoverCertificatesResult) ([]byte, error) { 11 | w := util.NewWriter() 12 | 13 | if result.TotalCertificates != uint32(len(result.Certificates)) { 14 | return nil, fmt.Errorf("total certificates %d does not match length of certificates slice %d", result.TotalCertificates, len(result.Certificates)) 15 | } 16 | 17 | // Write total certificates 18 | w.WriteVarInt(uint64(result.TotalCertificates)) 19 | 20 | // Write certificates 21 | for _, cert := range result.Certificates { 22 | certBytes, err := SerializeIdentityCertificate(&cert) 23 | if err != nil { 24 | return nil, fmt.Errorf("error serializing certificate: %w", err) 25 | } 26 | w.WriteBytes(certBytes) 27 | } 28 | 29 | return w.Buf, nil 30 | } 31 | 32 | func DeserializeDiscoverCertificatesResult(data []byte) (*wallet.DiscoverCertificatesResult, error) { 33 | r := util.NewReaderHoldError(data) 34 | result := &wallet.DiscoverCertificatesResult{} 35 | 36 | // Read total certificates 37 | result.TotalCertificates = uint32(r.ReadVarInt()) 38 | 39 | // Read certificates 40 | if result.TotalCertificates > 0 { 41 | result.Certificates = make([]wallet.IdentityCertificate, 0, result.TotalCertificates) 42 | } 43 | for i := uint32(0); i < result.TotalCertificates; i++ { 44 | cert, err := DeserializeIdentityCertificate(r) 45 | if err != nil { 46 | return nil, fmt.Errorf("error deserializing certificate: %w", err) 47 | } 48 | result.Certificates = append(result.Certificates, *cert) 49 | } 50 | 51 | r.CheckComplete() 52 | if r.Err != nil { 53 | return nil, fmt.Errorf("error deserializing DiscoverCertificates result: %w", r.Err) 54 | } 55 | 56 | return result, nil 57 | } 58 | -------------------------------------------------------------------------------- /wallet/serializer/discover_by_attributes.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | 7 | "github.com/bsv-blockchain/go-sdk/util" 8 | "github.com/bsv-blockchain/go-sdk/wallet" 9 | ) 10 | 11 | func SerializeDiscoverByAttributesArgs(args *wallet.DiscoverByAttributesArgs) ([]byte, error) { 12 | w := util.NewWriter() 13 | 14 | // Write attributes 15 | attributeKeys := make([]string, 0, len(args.Attributes)) 16 | for k := range args.Attributes { 17 | attributeKeys = append(attributeKeys, k) 18 | } 19 | sort.Strings(attributeKeys) 20 | w.WriteVarInt(uint64(len(attributeKeys))) 21 | for _, key := range attributeKeys { 22 | w.WriteIntBytes([]byte(key)) 23 | w.WriteIntBytes([]byte(args.Attributes[key])) 24 | } 25 | 26 | // Write limit, offset, seek permission 27 | w.WriteOptionalUint32(args.Limit) 28 | w.WriteOptionalUint32(args.Offset) 29 | w.WriteOptionalBool(args.SeekPermission) 30 | 31 | return w.Buf, nil 32 | } 33 | 34 | func DeserializeDiscoverByAttributesArgs(data []byte) (*wallet.DiscoverByAttributesArgs, error) { 35 | r := util.NewReaderHoldError(data) 36 | args := &wallet.DiscoverByAttributesArgs{ 37 | Attributes: make(map[string]string), 38 | } 39 | 40 | // Read attributes 41 | attributesLength := r.ReadVarInt() 42 | for i := uint64(0); i < attributesLength; i++ { 43 | fieldKey := string(r.ReadIntBytes()) 44 | fieldValue := string(r.ReadIntBytes()) 45 | 46 | if r.Err != nil { 47 | return nil, fmt.Errorf("error reading attribute %d: %w", i, r.Err) 48 | } 49 | 50 | args.Attributes[fieldKey] = fieldValue 51 | } 52 | 53 | // Read limit, offset, seek permission 54 | args.Limit = r.ReadOptionalUint32() 55 | args.Offset = r.ReadOptionalUint32() 56 | args.SeekPermission = r.ReadOptionalBool() 57 | 58 | r.CheckComplete() 59 | if r.Err != nil { 60 | return nil, fmt.Errorf("error deserializing DiscoverByAttributes args: %w", r.Err) 61 | } 62 | 63 | return args, nil 64 | } 65 | -------------------------------------------------------------------------------- /docs/examples/fee_modeling/fee_modeling.go: -------------------------------------------------------------------------------- 1 | package feemodeling 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/bsv-blockchain/go-sdk/transaction" 7 | "github.com/bsv-blockchain/go-sdk/util" 8 | ) 9 | 10 | type Example struct { 11 | Value int 12 | } 13 | 14 | func (e *Example) ComputeFee(tx transaction.Transaction) uint64 { 15 | // Version 3301 transactions are free :) 16 | if tx.Version == 3301 { 17 | return 0 18 | } 19 | 20 | // Compute the (potentially estimated) size of the transaction 21 | size := 4 // version 22 | size += util.VarInt(uint64(len(tx.Inputs))).Length() // number of inputs 23 | for i := 0; i < len(tx.Inputs); i++ { 24 | input := tx.Inputs[i] 25 | size += 40 // txid, output index, sequence number 26 | var scriptLength int 27 | if input.UnlockingScript != nil { 28 | scriptLength = len(*input.UnlockingScript) 29 | } else { 30 | panic("All inputs must have an unlocking script or an unlocking script template for sat/kb fee computation.") 31 | } 32 | size += util.VarInt(scriptLength).Length() // unlocking script length 33 | size += scriptLength // unlocking script 34 | } 35 | size += util.VarInt(len(tx.Outputs)).Length() // number of outputs 36 | for _, out := range tx.Outputs { 37 | size += 8 // satoshis 38 | length := len(*out.LockingScript) 39 | size += util.VarInt(length).Length() // script length 40 | size += length // script 41 | } 42 | size += 4 // lock time 43 | fee := float64((size / 1000) * e.Value) 44 | 45 | // Now we apply our input and output rules 46 | // For the inputs incentive 47 | if uint64(len(tx.Inputs))/3 >= uint64(len(tx.Outputs)) { 48 | fee *= 0.8 49 | } 50 | // For the outputs penalty 51 | if uint64(len(tx.Outputs))/5 >= uint64(len(tx.Inputs)) { 52 | fee *= 1.1 53 | } 54 | 55 | // We'll use Math.ceil to ensure the miners get the extra satoshi. 56 | return uint64(math.Ceil(fee)) 57 | } 58 | -------------------------------------------------------------------------------- /docs/examples/set_source_tx_output/README.md: -------------------------------------------------------------------------------- 1 | # Setting Source Transaction Outputs for Inputs 2 | 3 | When signing transactions without full BEEF (source transactions), you must provide the previous output details for each input. 4 | 5 | - Problem: You used a UTXO API and do not have the source transaction payload. 6 | - Solution: For each input, fetch satoshis and locking script of the referenced outpoint and call `SetSourceTxOutput`. 7 | 8 | Example (Go): 9 | 10 | ```go 11 | // Given a transaction tx and an input at index 0 12 | lockingScript, _ := script.NewFromHex("76a914...88ac") 13 | tx.Inputs[0].SetSourceTxOutput(&transaction.TransactionOutput{ 14 | Satoshis: 15564838601, 15 | LockingScript: lockingScript, 16 | }) 17 | 18 | unlocker, _ := p2pkh.Unlock(priv, nil) 19 | us, _ := unlocker.Sign(tx, 0) 20 | tx.Inputs[0].UnlockingScript = us 21 | ``` 22 | 23 | This ensures signature-hash calculation has the required context (value + script) and allows `Sign` to work. 24 | 25 | ## Alternative: Provide the source output when adding the input 26 | 27 | If you already have the previous output details at the time you add the input, you can use `AddInputWithOutput` to attach the source output inline. This avoids a separate `SetSourceTxOutput` call: 28 | 29 | ```go 30 | tx := transaction.NewTransaction() 31 | unlocker, _ := p2pkh.Unlock(priv, nil) 32 | 33 | txid, _ := chainhash.NewHashFromHex("") 34 | lockingScript, _ := script.NewFromHex("76a914...88ac") 35 | 36 | tx.AddInputWithOutput(&transaction.TransactionInput{ 37 | SourceTXID: txid, 38 | SourceTxOutIndex: 0, 39 | UnlockingScriptTemplate: unlocker, 40 | }, &transaction.TransactionOutput{ 41 | LockingScript: lockingScript, 42 | Satoshis: 15564838601, 43 | }) 44 | 45 | // Now you can sign the transaction 46 | _ = tx.Sign() 47 | ``` 48 | 49 | See also: `docs/examples/create_tx_with_op_return/create_tx_with_op_return.go` for a complete working example of this approach. 50 | -------------------------------------------------------------------------------- /docs/examples/hd_key_from_xpub/README.md: -------------------------------------------------------------------------------- 1 | # HD Key From xPub Example 2 | 3 | This example demonstrates how to use the `bip32` compatibility package to convert an extended public key (xPub) string into an HD (Hierarchical Deterministic) key object. 4 | 5 | ## Overview 6 | 7 | The `hd_key_from_xpub` example showcases: 8 | 1. Starting with a standard xPub string. 9 | 2. Using `bip32.GetHDKeyFromExtendedPublicKey` to parse the xPub. 10 | 3. Verifying the properties of the resulting HD key object. 11 | 12 | ## Code Walkthrough 13 | 14 | ### Converting xPub to HD Key 15 | 16 | ```go 17 | // Start with an existing xPub 18 | xPub := "xpub661MyMwAqRbcH3WGvLjupmr43L1GVH3MP2WQWvdreDraBeFJy64Xxv4LLX9ZVWWz3ZjZkMuZtSsc9qH9JZR74bR4PWkmtEvP423r6DJR8kA" 19 | 20 | // Convert to a HD key 21 | key, err := bip32.GetHDKeyFromExtendedPublicKey(xPub) 22 | if err != nil { 23 | log.Fatalf("error occurred: %s", err.Error()) 24 | } 25 | 26 | log.Printf("converted key: %s private: %v", key.String(), key.IsPrivate()) 27 | ``` 28 | 29 | This section shows the direct conversion of an xPub string into an `*bip32.HDKey` object. The `IsPrivate()` method will return `false` for keys derived from an xPub. 30 | 31 | ## Running the Example 32 | 33 | To run this example: 34 | 35 | ```bash 36 | go run hd_key_from_xpub.go 37 | ``` 38 | 39 | **Note**: This example uses a predefined xPub string. You can replace it with any valid xPub to see the conversion. 40 | 41 | ## Integration Steps 42 | 43 | To integrate this functionality into your application: 44 | 1. Obtain an xPub string that you need to work with. 45 | 2. Use `bip32.GetHDKeyFromExtendedPublicKey(xPubString)` to get an HD key object. 46 | 3. You can then use this HD key object to derive child public keys. 47 | 48 | ## Additional Resources 49 | 50 | For more information, see: 51 | - [Package Documentation](https://pkg.go.dev/github.com/bsv-blockchain/go-sdk/compat/bip32) 52 | - [Generate HD Key Example](../generate_hd_key/) 53 | - [Derive Child Key Example](../derive_child/) -------------------------------------------------------------------------------- /docs/examples/verify_beef/verify_beef.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/bsv-blockchain/go-sdk/spv" 7 | "github.com/bsv-blockchain/go-sdk/transaction" 8 | ) 9 | 10 | // Replace with the BEEF structure you'd like to check 11 | const BEEFHex = "0100beef01fe636d0c0007021400fe507c0c7aa754cef1f7889d5fd395cf1f785dd7de98eed895dbedfe4e5bc70d1502ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e010b00bc4ff395efd11719b277694cface5aa50d085a0bb81f613f70313acd28cf4557010400574b2d9142b8d28b61d88e3b2c3f44d858411356b49a28a4643b6d1a6a092a5201030051a05fc84d531b5d250c23f4f886f6812f9fe3f402d61607f977b4ecd2701c19010000fd781529d58fc2523cf396a7f25440b409857e7e221766c57214b1d38c7b481f01010062f542f45ea3660f86c013ced80534cb5fd4c19d66c56e7e8c5d4bf2d40acc5e010100b121e91836fd7cd5102b654e9f72f3cf6fdbfd0b161c53a9c54b12c841126331020100000001cd4e4cac3c7b56920d1e7655e7e260d31f29d9a388d04910f1bbd72304a79029010000006b483045022100e75279a205a547c445719420aa3138bf14743e3f42618e5f86a19bde14bb95f7022064777d34776b05d816daf1699493fcdf2ef5a5ab1ad710d9c97bfb5b8f7cef3641210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013e660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac0000000001000100000001ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e000000006a47304402203a61a2e931612b4bda08d541cfb980885173b8dcf64a3471238ae7abcd368d6402204cbf24f04b9aa2256d8901f0ed97866603d2be8324c2bfb7a37bf8fc90edd5b441210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013c660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac0000000000" 12 | 13 | func main() { 14 | // You can create a Transaction from BEEF hex directly 15 | tx, err := transaction.NewTransactionFromBEEFHex(BEEFHex) 16 | if err != nil { 17 | panic(err) 18 | } 19 | // This ensures the BEEF structure is legitimate 20 | ctx := context.Background() 21 | verified, _ := tx.MerklePath.Verify(ctx, tx.TxID(), &spv.GullibleHeadersClient{}) 22 | println(verified) 23 | } 24 | -------------------------------------------------------------------------------- /overlay/topic/facilitator.go: -------------------------------------------------------------------------------- 1 | package topic 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "errors" 8 | "net/http" 9 | "time" 10 | 11 | "github.com/bsv-blockchain/go-sdk/overlay" 12 | "github.com/bsv-blockchain/go-sdk/util" 13 | ) 14 | 15 | const MAX_SHIP_QUERY_TIMEOUT = time.Second 16 | 17 | // Facilitator defines the interface for overlay broadcast facilitators that can send tagged BEEF to overlay services 18 | type Facilitator interface { 19 | Send(url string, taggedBEEF *overlay.TaggedBEEF) (*overlay.Steak, error) 20 | } 21 | 22 | // HTTPSOverlayBroadcastFacilitator implements the Facilitator interface using HTTPS requests for broadcasting transactions 23 | type HTTPSOverlayBroadcastFacilitator struct { 24 | Client util.HTTPClient 25 | } 26 | 27 | // Send broadcasts a tagged BEEF transaction to the specified overlay service URL and returns the STEAK response 28 | func (f *HTTPSOverlayBroadcastFacilitator) Send(url string, taggedBEEF *overlay.TaggedBEEF) (*overlay.Steak, error) { 29 | timeoutContext, cancel := context.WithTimeout(context.Background(), MAX_SHIP_QUERY_TIMEOUT) 30 | defer cancel() 31 | 32 | req, err := http.NewRequestWithContext(timeoutContext, "POST", url+"/submit", bytes.NewBuffer(taggedBEEF.Beef)) 33 | if err != nil { 34 | return nil, err 35 | } 36 | if topics, err := json.Marshal(taggedBEEF.Topics); err != nil { 37 | return nil, err 38 | } else { 39 | req.Header.Set("Content-Type", "application/octet-stream") 40 | req.Header.Set("X-Topics", string(topics)) 41 | resp, err := f.Client.Do(req) 42 | if err != nil { 43 | return nil, err 44 | } 45 | defer resp.Body.Close() 46 | if resp.StatusCode != http.StatusOK { 47 | return nil, &util.HTTPError{ 48 | StatusCode: resp.StatusCode, 49 | Err: errors.New("lookup failed"), 50 | } 51 | } 52 | steak := &overlay.Steak{} 53 | if err := json.NewDecoder(resp.Body).Decode(&steak); err != nil { 54 | return nil, err 55 | } 56 | return steak, nil 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /transaction/fees.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "slices" 5 | 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | type ChangeDistribution int 10 | 11 | var ( 12 | ChangeDistributionEqual ChangeDistribution = 1 13 | ChangeDistributionRandom ChangeDistribution = 2 14 | ) 15 | 16 | type FeeModel interface { 17 | ComputeFee(tx *Transaction) (uint64, error) 18 | } 19 | 20 | // Fee computes the fee for the transaction. 21 | func (tx *Transaction) Fee(f FeeModel, changeDistribution ChangeDistribution) error { 22 | fee, err := f.ComputeFee(tx) 23 | if err != nil { 24 | return err 25 | } 26 | satsIn := uint64(0) 27 | for _, i := range tx.Inputs { 28 | sourceSats := i.SourceTxSatoshis() 29 | if sourceSats == nil { 30 | return ErrEmptyPreviousTx 31 | } 32 | satsIn += *sourceSats 33 | } 34 | satsOut := uint64(0) 35 | changeOuts := uint64(0) 36 | for _, o := range tx.Outputs { 37 | if !o.Change { 38 | satsOut += o.Satoshis 39 | } else { 40 | changeOuts++ 41 | } 42 | } 43 | if satsIn < satsOut+fee { 44 | return ErrInsufficientInputs 45 | } 46 | change := satsIn - satsOut - fee 47 | // There is not enough change to distribute among the change outputs. 48 | // We'll remove all change outputs and leave the extra for the miners. 49 | if changeOuts > change { 50 | tx.Outputs = slices.DeleteFunc(tx.Outputs, func(o *TransactionOutput) bool { 51 | return o.Change 52 | }) 53 | } else { 54 | switch changeDistribution { 55 | case ChangeDistributionRandom: 56 | return errors.New("not-implemented") 57 | case ChangeDistributionEqual: 58 | changePerOutput := change / changeOuts 59 | for _, o := range tx.Outputs { 60 | if o.Change { 61 | o.Satoshis = changePerOutput 62 | } 63 | } 64 | } 65 | } 66 | return nil 67 | } 68 | 69 | func (tx *Transaction) GetFee() (total uint64, err error) { 70 | if totalIn, err := tx.TotalInputSatoshis(); err != nil { 71 | return 0, err 72 | } else { 73 | return totalIn - tx.TotalOutputSatoshis(), nil 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /transaction/merkletreeparent.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "encoding/hex" 5 | 6 | "github.com/bsv-blockchain/go-sdk/chainhash" 7 | crypto "github.com/bsv-blockchain/go-sdk/primitives/hash" 8 | "github.com/bsv-blockchain/go-sdk/util" 9 | ) 10 | 11 | // MerkleTreeParentStr returns the Merkle Tree parent of two MerkleTree children using hex strings instead of bytes. 12 | func MerkleTreeParentStr(leftNode, rightNode string) (string, error) { 13 | l, err := hex.DecodeString(leftNode) 14 | if err != nil { 15 | return "", err 16 | } 17 | r, err := hex.DecodeString(rightNode) 18 | if err != nil { 19 | return "", err 20 | } 21 | 22 | return hex.EncodeToString(MerkleTreeParentBytes(l, r)), nil 23 | } 24 | 25 | // MerkleTreeParentBytes returns the Merkle Tree parent of two MerkleTree children. 26 | func MerkleTreeParentBytes(leftNode, rightNode []byte) []byte { 27 | concatenated := flipTwoArrays(leftNode, rightNode) 28 | 29 | hash := crypto.Sha256d(concatenated) 30 | 31 | util.ReverseBytesInPlace(hash) 32 | 33 | return hash 34 | } 35 | 36 | // flipTwoArrays reverses two byte arrays individually and returns as one concatenated slice 37 | // example: 38 | // for a=[a, b, c], b=[d, e, f] the result is [c, b, a, f, e, d] 39 | func flipTwoArrays(a, b []byte) []byte { 40 | result := make([]byte, 0, len(a)+len(b)) 41 | for i := len(a) - 1; i >= 0; i-- { 42 | result = append(result, a[i]) 43 | } 44 | for i := len(b) - 1; i >= 0; i-- { 45 | result = append(result, b[i]) 46 | } 47 | return result 48 | } 49 | 50 | // MerkleTreeParent returns the Merkle Tree parent of two Merkle Tree children. 51 | // The expectation is that the bytes are not reversed. 52 | func MerkleTreeParent(l *chainhash.Hash, r *chainhash.Hash) *chainhash.Hash { 53 | concatenated := make([]byte, len(l)+len(r)) 54 | copy(concatenated, l[:]) 55 | copy(concatenated[len(l):], r[:]) 56 | hash, err := chainhash.NewHash(crypto.Sha256d(concatenated)) 57 | if err != nil { 58 | return &chainhash.Hash{} 59 | } 60 | return hash 61 | } 62 | -------------------------------------------------------------------------------- /wallet/serializer/relinquish_certificate_test.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "testing" 5 | 6 | tu "github.com/bsv-blockchain/go-sdk/util/test_util" 7 | "github.com/bsv-blockchain/go-sdk/wallet" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestRelinquishCertificateArgs(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | args *wallet.RelinquishCertificateArgs 15 | }{{ 16 | name: "full args", 17 | args: &wallet.RelinquishCertificateArgs{ 18 | Type: [32]byte{1}, 19 | SerialNumber: [32]byte{2}, 20 | Certifier: tu.GetPKFromBytes([]byte{3}), 21 | }, 22 | }, { 23 | name: "minimal args", 24 | args: &wallet.RelinquishCertificateArgs{ 25 | Type: [32]byte{4}, 26 | SerialNumber: [32]byte{5}, 27 | Certifier: tu.GetPKFromBytes([]byte{6}), 28 | }, 29 | }} 30 | 31 | for _, tt := range tests { 32 | t.Run(tt.name, func(t *testing.T) { 33 | // Test serialization 34 | data, err := SerializeRelinquishCertificateArgs(tt.args) 35 | require.NoError(t, err, "serializing RelinquishCertificateArgs should not error") 36 | 37 | // Test deserialization 38 | got, err := DeserializeRelinquishCertificateArgs(data) 39 | require.NoError(t, err, "deserializing RelinquishCertificateArgs should not error") 40 | 41 | // Compare results 42 | require.Equal(t, tt.args, got, "deserialized args should match original args") 43 | }) 44 | } 45 | } 46 | 47 | func TestRelinquishCertificateResult(t *testing.T) { 48 | t.Run("success", func(t *testing.T) { 49 | result := &wallet.RelinquishCertificateResult{Relinquished: true} 50 | data, err := SerializeRelinquishCertificateResult(result) 51 | require.NoError(t, err, "serializing RelinquishCertificateResult should not error") 52 | 53 | got, err := DeserializeRelinquishCertificateResult(data) 54 | require.NoError(t, err, "deserializing RelinquishCertificateResult should not error") 55 | require.Equal(t, result, got, "deserialized result should match original result") 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /wallet/serializer/relinquish_output_test.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | tu "github.com/bsv-blockchain/go-sdk/util/test_util" 5 | "github.com/bsv-blockchain/go-sdk/wallet" 6 | "github.com/stretchr/testify/require" 7 | "testing" 8 | ) 9 | 10 | func TestRelinquishOutputArgs(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | args *wallet.RelinquishOutputArgs 14 | }{ 15 | { 16 | name: "basic args", 17 | args: &wallet.RelinquishOutputArgs{ 18 | Basket: "test-basket", 19 | Output: *tu.OutpointFromString(t, "8a552c995db3602e85bb9df911803897d1ea17ba5cdd198605d014be49db9f72.0"), 20 | }, 21 | }, 22 | { 23 | name: "empty basket", 24 | args: &wallet.RelinquishOutputArgs{ 25 | Basket: "", 26 | Output: *tu.OutpointFromString(t, "8a552c995db3602e85bb9df911803897d1ea17ba5cdd198605d014be49db9f72.1"), 27 | }, 28 | }, 29 | } 30 | 31 | for _, tt := range tests { 32 | t.Run(tt.name, func(t *testing.T) { 33 | // Test serialization 34 | data, err := SerializeRelinquishOutputArgs(tt.args) 35 | require.NoError(t, err, "serializing RelinquishOutputArgs should not error") 36 | 37 | // Test deserialization 38 | got, err := DeserializeRelinquishOutputArgs(data) 39 | require.NoError(t, err, "deserializing RelinquishOutputArgs should not error") 40 | 41 | // Compare results 42 | require.Equal(t, tt.args, got, "deserialized args should match original args") 43 | }) 44 | } 45 | } 46 | 47 | func TestRelinquishOutputResult(t *testing.T) { 48 | t.Run("successful relinquish", func(t *testing.T) { 49 | result := &wallet.RelinquishOutputResult{Relinquished: true} 50 | data, err := SerializeRelinquishOutputResult(result) 51 | require.NoError(t, err, "serializing successful RelinquishOutputResult should not error") 52 | 53 | got, err := DeserializeRelinquishOutputResult(data) 54 | require.NoError(t, err, "deserializing successful RelinquishOutputResult should not error") 55 | require.Equal(t, result, got, "deserialized successful result should match original") 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /wallet/serializer/relinquish_certificate.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "fmt" 5 | 6 | ec "github.com/bsv-blockchain/go-sdk/primitives/ec" 7 | "github.com/bsv-blockchain/go-sdk/util" 8 | "github.com/bsv-blockchain/go-sdk/wallet" 9 | ) 10 | 11 | func SerializeRelinquishCertificateArgs(args *wallet.RelinquishCertificateArgs) ([]byte, error) { 12 | w := util.NewWriter() 13 | 14 | // Encode type (base64) 15 | if args.Type == [32]byte{} { 16 | return nil, fmt.Errorf("type is empty") 17 | } 18 | w.WriteBytes(args.Type[:]) 19 | 20 | // Encode serialNumber (base64) 21 | if args.SerialNumber == [32]byte{} { 22 | return nil, fmt.Errorf("serialNumber is empty") 23 | } 24 | w.WriteBytes(args.SerialNumber[:]) 25 | 26 | // Encode certifier (hex) 27 | if args.Certifier == nil { 28 | return nil, fmt.Errorf("certifier is empty") 29 | } 30 | w.WriteBytes(args.Certifier.Compressed()) 31 | 32 | return w.Buf, nil 33 | } 34 | 35 | func DeserializeRelinquishCertificateArgs(data []byte) (*wallet.RelinquishCertificateArgs, error) { 36 | r := util.NewReaderHoldError(data) 37 | args := &wallet.RelinquishCertificateArgs{} 38 | 39 | // Read type (base64), serialNumber (base64), certifier (hex) 40 | copy(args.Type[:], r.ReadBytes(sizeType)) 41 | copy(args.SerialNumber[:], r.ReadBytes(sizeSerial)) 42 | 43 | parsedCertifier, err := ec.PublicKeyFromBytes(r.ReadBytes(sizePubKey)) 44 | if err != nil { 45 | return nil, fmt.Errorf("error parsing certifier public key: %w", err) 46 | } 47 | args.Certifier = parsedCertifier 48 | 49 | r.CheckComplete() 50 | if r.Err != nil { 51 | return nil, fmt.Errorf("error deserializing RelinquishCertificate args: %w", r.Err) 52 | } 53 | 54 | return args, nil 55 | } 56 | 57 | func SerializeRelinquishCertificateResult(_ *wallet.RelinquishCertificateResult) ([]byte, error) { 58 | return nil, nil 59 | } 60 | 61 | func DeserializeRelinquishCertificateResult(_ []byte) (*wallet.RelinquishCertificateResult, error) { 62 | return &wallet.RelinquishCertificateResult{ 63 | Relinquished: true, 64 | }, nil 65 | } 66 | -------------------------------------------------------------------------------- /transaction/fee_model/sats_per_kb_test.go: -------------------------------------------------------------------------------- 1 | package feemodel 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestCalculateFee(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | txSize int 13 | satoshisPerKB uint64 14 | expectedFee uint64 15 | description string 16 | }{ 17 | { 18 | name: "240 bytes at 100 sats/KB", 19 | txSize: 240, 20 | satoshisPerKB: 100, 21 | expectedFee: 24, 22 | description: "240/1000 * 100 = 24 - tests the bug where casting happened before multiplication", 23 | }, 24 | { 25 | name: "240 bytes at 1 sat/KB", 26 | txSize: 240, 27 | satoshisPerKB: 1, 28 | expectedFee: 1, 29 | description: "Edge case that would pass even with buggy implementation", 30 | }, 31 | { 32 | name: "240 bytes at 10 sats/KB", 33 | txSize: 240, 34 | satoshisPerKB: 10, 35 | expectedFee: 3, 36 | description: "240/1000 * 10 = 2.4, ceil = 3", 37 | }, 38 | { 39 | name: "250 bytes at 500 sats/KB", 40 | txSize: 250, 41 | satoshisPerKB: 500, 42 | expectedFee: 125, 43 | description: "250/1000 * 500 = 125", 44 | }, 45 | { 46 | name: "1000 bytes at 100 sats/KB", 47 | txSize: 1000, 48 | satoshisPerKB: 100, 49 | expectedFee: 100, 50 | description: "1000/1000 * 100 = 100", 51 | }, 52 | { 53 | name: "1500 bytes at 100 sats/KB", 54 | txSize: 1500, 55 | satoshisPerKB: 100, 56 | expectedFee: 150, 57 | description: "1500/1000 * 100 = 150", 58 | }, 59 | { 60 | name: "1500 bytes at 500 sats/KB", 61 | txSize: 1500, 62 | satoshisPerKB: 500, 63 | expectedFee: 750, 64 | description: "1500/1000 * 500 = 750", 65 | }, 66 | } 67 | 68 | for _, tt := range tests { 69 | t.Run(tt.name, func(t *testing.T) { 70 | fee := calculateFee(tt.txSize, tt.satoshisPerKB) 71 | require.Equal(t, tt.expectedFee, fee, tt.description) 72 | }) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /docs/examples/verify_transaction/verify_transaction.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/bsv-blockchain/go-sdk/spv" 7 | "github.com/bsv-blockchain/go-sdk/transaction" 8 | ) 9 | 10 | // Replace with the BEEF structure you'd like to check 11 | const BEEFHex = "0100beef01fe636d0c0007021400fe507c0c7aa754cef1f7889d5fd395cf1f785dd7de98eed895dbedfe4e5bc70d1502ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e010b00bc4ff395efd11719b277694cface5aa50d085a0bb81f613f70313acd28cf4557010400574b2d9142b8d28b61d88e3b2c3f44d858411356b49a28a4643b6d1a6a092a5201030051a05fc84d531b5d250c23f4f886f6812f9fe3f402d61607f977b4ecd2701c19010000fd781529d58fc2523cf396a7f25440b409857e7e221766c57214b1d38c7b481f01010062f542f45ea3660f86c013ced80534cb5fd4c19d66c56e7e8c5d4bf2d40acc5e010100b121e91836fd7cd5102b654e9f72f3cf6fdbfd0b161c53a9c54b12c841126331020100000001cd4e4cac3c7b56920d1e7655e7e260d31f29d9a388d04910f1bbd72304a79029010000006b483045022100e75279a205a547c445719420aa3138bf14743e3f42618e5f86a19bde14bb95f7022064777d34776b05d816daf1699493fcdf2ef5a5ab1ad710d9c97bfb5b8f7cef3641210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013e660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac0000000001000100000001ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e000000006a47304402203a61a2e931612b4bda08d541cfb980885173b8dcf64a3471238ae7abcd368d6402204cbf24f04b9aa2256d8901f0ed97866603d2be8324c2bfb7a37bf8fc90edd5b441210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013c660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac0000000000" 12 | 13 | func main() { 14 | // You can create a Transaction from BEEF hex directly 15 | tx, err := transaction.NewTransactionFromBEEFHex(BEEFHex) 16 | if err != nil { 17 | panic(err) 18 | } 19 | // This ensures the BEEF structure is legitimate, the scripts are valid, and the merkle path is correct 20 | // Also optionally verifies fees 21 | ctx := context.Background() 22 | verified, _ := spv.Verify(ctx, tx, &spv.GullibleHeadersClient{}, nil) 23 | println(verified) 24 | } 25 | -------------------------------------------------------------------------------- /script/interpreter/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013-2017 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package interpreter implements the bitcoin transaction script language. 7 | 8 | A complete description of the script language used by bitcoin can be found at 9 | https://en.bitcoin.it/wiki/Script. The following only serves as a quick 10 | overview to provide information on how to use the package. 11 | 12 | This package provides data structures and functions to parse and execute 13 | bitcoin transaction scripts. 14 | 15 | # Script Overview 16 | 17 | Bitcoin transaction scripts are written in a stack-base, FORTH-like language. 18 | 19 | The bitcoin script language consists of a number of opcodes which fall into 20 | several categories such pushing and popping data to and from the stack, 21 | performing basic and bitwise arithmetic, conditional branching, comparing 22 | hashes, and checking cryptographic signatures. Scripts are processed from left 23 | to right and intentionally do not provide loops. 24 | 25 | The vast majority of Bitcoin scripts at the time of this writing are of several 26 | standard forms which consist of a spender providing a public key and a signature 27 | which proves the spender owns the associated private key. This information 28 | is used to prove the spender is authorized to perform the transaction. 29 | 30 | One benefit of using a scripting language is added flexibility in specifying 31 | what conditions must be met in order to spend bitcoins. 32 | 33 | # Errors 34 | 35 | Errors returned by this package are of type interpreter.Error. This allows the 36 | caller to programmatically determine the specific error by examining the 37 | ErrorCode field of the type asserted interpreter.Error while still providing rich 38 | error messages with contextual information. A convenience function named 39 | IsErrorCode is also provided to allow callers to easily check for a specific 40 | error code. See ErrorCode in the package documentation for a full list. 41 | */ 42 | package interpreter 43 | -------------------------------------------------------------------------------- /transaction/sighash/flag.go: -------------------------------------------------------------------------------- 1 | // Package sighash comment 2 | package transaction 3 | 4 | // Flag represents hash type bits at the end of a signature. 5 | type Flag uint8 6 | 7 | // SIGHASH type bits from the end of a signature. 8 | // see: https://wiki.bitcoinsv.io/index.php/SIGHASH_flags 9 | const ( 10 | Old Flag = 0x0 11 | All Flag = 0x1 12 | None Flag = 0x2 13 | Single Flag = 0x3 14 | AnyOneCanPay Flag = 0x80 15 | 16 | // Currently, all BitCoin (SV) transactions require an additional SIGHASH flag (after UAHF) 17 | 18 | AllForkID Flag = 0x1 | 0x40 19 | NoneForkID Flag = 0x2 | 0x40 20 | SingleForkID Flag = 0x3 | 0x40 21 | AnyOneCanPayForkID Flag = 0x80 | 0x40 22 | 23 | // ForkID is the replay protected signature hash flag 24 | // used by the Uahf hardfork. 25 | 26 | ForkID Flag = 0x40 27 | 28 | // Mask defines the number of bits of the hash type which is used 29 | // to identify which outputs are signed. 30 | Mask = 0x1f 31 | ) 32 | 33 | // Has returns true if contains the provided flag. 34 | func (f Flag) Has(shf Flag) bool { 35 | return f&shf == shf 36 | } 37 | 38 | // HasWithMask returns true if contains the provided flag masked 39 | func (f Flag) HasWithMask(shf Flag) bool { 40 | return f&Mask == shf 41 | } 42 | 43 | func (f Flag) String() string { 44 | switch f { //nolint:exhaustive // not needed 45 | case All: 46 | return "ALL" 47 | case None: 48 | return "NONE" 49 | case Single: 50 | return "SINGLE" 51 | case All | AnyOneCanPay: 52 | return "ALL|ANYONECANPAY" 53 | case None | AnyOneCanPay: 54 | return "NONE|ANYONECANPAY" 55 | case Single | AnyOneCanPay: 56 | return "SINGLE|ANYONECANPAY" 57 | case AllForkID: 58 | return "ALL|FORKID" 59 | case NoneForkID: 60 | return "NONE|FORKID" 61 | case SingleForkID: 62 | return "SINGLE|FORKID" 63 | case AllForkID | AnyOneCanPay: 64 | return "ALL|FORKID|ANYONECANPAY" 65 | case NoneForkID | AnyOneCanPay: 66 | return "NONE|FORKID|ANYONECANPAY" 67 | case SingleForkID | AnyOneCanPay: 68 | return "SINGLE|FORKID|ANYONECANPAY" 69 | } 70 | 71 | return "ALL" 72 | } 73 | -------------------------------------------------------------------------------- /wallet/serializer/authenticated_test.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "github.com/bsv-blockchain/go-sdk/wallet" 5 | "github.com/stretchr/testify/require" 6 | "testing" 7 | ) 8 | 9 | func TestIsAuthenticatedResult(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | input *wallet.AuthenticatedResult 13 | expected bool 14 | }{ 15 | { 16 | name: "authenticated true", 17 | input: &wallet.AuthenticatedResult{Authenticated: true}, 18 | expected: true, 19 | }, 20 | { 21 | name: "authenticated false", 22 | input: &wallet.AuthenticatedResult{Authenticated: false}, 23 | expected: false, 24 | }, 25 | } 26 | 27 | for _, tt := range tests { 28 | t.Run(tt.name, func(t *testing.T) { 29 | // Test serialization 30 | data, err := SerializeIsAuthenticatedResult(tt.input) 31 | require.NoError(t, err) 32 | require.Equal(t, 1, len(data)) // auth byte 33 | 34 | // Test deserialization 35 | result, err := DeserializeIsAuthenticatedResult(data) 36 | require.NoError(t, err) 37 | require.Equal(t, tt.expected, result.Authenticated) 38 | }) 39 | } 40 | } 41 | 42 | func TestIsAuthenticatedResultErrors(t *testing.T) { 43 | tests := []struct { 44 | name string 45 | data []byte 46 | wantError string 47 | }{ 48 | { 49 | name: "empty data", 50 | data: []byte{}, 51 | wantError: "invalid data length", 52 | }, 53 | } 54 | 55 | for _, tt := range tests { 56 | t.Run(tt.name, func(t *testing.T) { 57 | _, err := DeserializeIsAuthenticatedResult(tt.data) 58 | require.Error(t, err) 59 | require.Contains(t, err.Error(), tt.wantError) 60 | }) 61 | } 62 | } 63 | 64 | func TestWaitAuthenticatedResult(t *testing.T) { 65 | // Test serialization 66 | input := &wallet.AuthenticatedResult{Authenticated: false} 67 | data, err := SerializeWaitAuthenticatedResult(input) 68 | require.NoError(t, err) 69 | require.Nil(t, data) 70 | 71 | // Test deserialization 72 | result, err := DeserializeWaitAuthenticatedResult(nil) 73 | require.NoError(t, err) 74 | require.True(t, result.Authenticated) 75 | } 76 | -------------------------------------------------------------------------------- /wallet/substrates/testdata/discoverByAttributes-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "totalCertificates": 1, 4 | "certificates": [ 5 | { 6 | "type": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0ZXN0LXR5cGU=", 7 | "serialNumber": "AAAAAAAAAAAAAAAAAAB0ZXN0LXNlcmlhbC1udW1iZXI=", 8 | "subject": "025ad43a22ac38d0bc1f8bacaabb323b5d634703b7a774c4268f6a09e4ddf79097", 9 | "certifier": "0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1", 10 | "revocationOutpoint": "aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d.0", 11 | "fields": { 12 | "name": "Alice", 13 | "email": "alice@example.com" 14 | }, 15 | "signature": "3045022100a6f09ee70382ab364f3f6b040aebb8fe7a51dbc3b4c99cfeb2f7756432162833022067349b91a6319345996faddf36d1b2f3a502e4ae002205f9d2db85474f9aed5a", 16 | "certifierInfo": { 17 | "name": "Test Certifier", 18 | "iconUrl": "https://example.com/icon.png", 19 | "description": "Certifier description", 20 | "trust": 5 21 | }, 22 | "publiclyRevealedKeyring": { 23 | "pubField": "AlrUOiKsONC8H4usqrsyO11jRwO3p3TEJo9qCeTd95CX" 24 | }, 25 | "decryptedFields": { 26 | "name": "Alice" 27 | } 28 | } 29 | ] 30 | }, 31 | "wire": "0001fd0e010000000000000000000000000000000000000000000000746573742d747970650000000000000000000000000000746573742d73657269616c2d6e756d626572025ad43a22ac38d0bc1f8bacaabb323b5d634703b7a774c4268f6a09e4ddf790970294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d000205656d61696c11616c696365406578616d706c652e636f6d046e616d6505416c6963653045022100a6f09ee70382ab364f3f6b040aebb8fe7a51dbc3b4c99cfeb2f7756432162833022067349b91a6319345996faddf36d1b2f3a502e4ae002205f9d2db85474f9aed5a0e54657374204365727469666965721c68747470733a2f2f6578616d706c652e636f6d2f69636f6e2e706e6715436572746966696572206465736372697074696f6e0501087075624669656c6421025ad43a22ac38d0bc1f8bacaabb323b5d634703b7a774c4268f6a09e4ddf7909701046e616d6505416c696365" 32 | } -------------------------------------------------------------------------------- /wallet/substrates/testdata/discoverByIdentityKey-simple-result.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "totalCertificates": 1, 4 | "certificates": [ 5 | { 6 | "type": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0ZXN0LXR5cGU=", 7 | "serialNumber": "AAAAAAAAAAAAAAAAAAB0ZXN0LXNlcmlhbC1udW1iZXI=", 8 | "subject": "025ad43a22ac38d0bc1f8bacaabb323b5d634703b7a774c4268f6a09e4ddf79097", 9 | "certifier": "0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1", 10 | "revocationOutpoint": "aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d.0", 11 | "fields": { 12 | "name": "Alice", 13 | "email": "alice@example.com" 14 | }, 15 | "signature": "3045022100a6f09ee70382ab364f3f6b040aebb8fe7a51dbc3b4c99cfeb2f7756432162833022067349b91a6319345996faddf36d1b2f3a502e4ae002205f9d2db85474f9aed5a", 16 | "certifierInfo": { 17 | "name": "Test Certifier", 18 | "iconUrl": "https://example.com/icon.png", 19 | "description": "Certifier description", 20 | "trust": 5 21 | }, 22 | "publiclyRevealedKeyring": { 23 | "pubField": "AlrUOiKsONC8H4usqrsyO11jRwO3p3TEJo9qCeTd95CX" 24 | }, 25 | "decryptedFields": { 26 | "name": "Alice" 27 | } 28 | } 29 | ] 30 | }, 31 | "wire": "0001fd0e010000000000000000000000000000000000000000000000746573742d747970650000000000000000000000000000746573742d73657269616c2d6e756d626572025ad43a22ac38d0bc1f8bacaabb323b5d634703b7a774c4268f6a09e4ddf790970294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d000205656d61696c11616c696365406578616d706c652e636f6d046e616d6505416c6963653045022100a6f09ee70382ab364f3f6b040aebb8fe7a51dbc3b4c99cfeb2f7756432162833022067349b91a6319345996faddf36d1b2f3a502e4ae002205f9d2db85474f9aed5a0e54657374204365727469666965721c68747470733a2f2f6578616d706c652e636f6d2f69636f6e2e706e6715436572746966696572206465736372697074696f6e0501087075624669656c6421025ad43a22ac38d0bc1f8bacaabb323b5d634703b7a774c4268f6a09e4ddf7909701046e616d6505416c696365" 32 | } -------------------------------------------------------------------------------- /primitives/drbg/drbg_test.go: -------------------------------------------------------------------------------- 1 | package primitives 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | // DRBGVector represents a single test vector for DRBG testing 11 | type DRBGVector struct { 12 | Name string `json:"name"` 13 | Entropy string `json:"entropy"` 14 | Nonce string `json:"nonce"` 15 | Add []string `json:"add"` // Changed to a slice of strings if it's an array 16 | Expected string `json:"expected"` 17 | } 18 | 19 | // ReadDRBGVectors reads and parses the test vectors from a JSON file 20 | func ReadDRBGVectors(filename string) ([]DRBGVector, error) { 21 | file, err := os.ReadFile(filename) 22 | if err != nil { 23 | return nil, err 24 | } 25 | var vectors []DRBGVector 26 | err = json.Unmarshal(file, &vectors) 27 | return vectors, err 28 | } 29 | func TestHmacDRBG(t *testing.T) { 30 | vectors, err := ReadDRBGVectors("testdata/vectors.json") 31 | if err != nil { 32 | t.Fatalf("Failed to read DRBG vectors: %v", err) 33 | } 34 | 35 | for _, vector := range vectors { 36 | t.Run(vector.Name, func(t *testing.T) { 37 | entropy, _ := hex.DecodeString(vector.Entropy) 38 | nonce, _ := hex.DecodeString(vector.Nonce) 39 | expected, _ := hex.DecodeString(vector.Expected) 40 | 41 | t.Logf("Testing vector: %s\n", vector.Name) 42 | t.Logf("Entropy: %x, Nonce: %x, Expected Length: %d\n", entropy, nonce, len(expected)) 43 | 44 | drbg, err := NewDRBG(entropy, nonce) 45 | if err != nil { 46 | t.Fatalf("Failed to create DRBG: %v", err) 47 | } 48 | 49 | var last []byte 50 | for range vector.Add { 51 | last, _ = drbg.Generate(len(expected)) // Ensure this is the correct length 52 | } 53 | 54 | if !compareSlices(last, expected) { 55 | t.Errorf("DRBG output did not match expected for %s\nExpected: %x\nGot: %x", vector.Name, expected, last) 56 | } 57 | }) 58 | } 59 | } 60 | 61 | // compareSlices compares two byte slices for equality 62 | func compareSlices(a, b []byte) bool { 63 | if len(a) != len(b) { 64 | return false 65 | } 66 | for i, v := range a { 67 | if v != b[i] { 68 | return false 69 | } 70 | } 71 | return true 72 | } 73 | -------------------------------------------------------------------------------- /wallet/serializer/get_header_test.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | tu "github.com/bsv-blockchain/go-sdk/util/test_util" 5 | "github.com/bsv-blockchain/go-sdk/wallet" 6 | "github.com/stretchr/testify/require" 7 | "testing" 8 | ) 9 | 10 | func TestGetHeaderArgs(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | args *wallet.GetHeaderArgs 14 | }{ 15 | { 16 | name: "height 1", 17 | args: &wallet.GetHeaderArgs{Height: 1}, 18 | }, 19 | { 20 | name: "height 100000", 21 | args: &wallet.GetHeaderArgs{Height: 100000}, 22 | }, 23 | } 24 | 25 | for _, tt := range tests { 26 | t.Run(tt.name, func(t *testing.T) { 27 | // Test serialization 28 | data, err := SerializeGetHeaderArgs(tt.args) 29 | require.NoError(t, err, "serializing GetHeaderArgs should not error") 30 | 31 | // Test deserialization 32 | got, err := DeserializeGetHeaderArgs(data) 33 | require.NoError(t, err, "deserializing GetHeaderArgs should not error") 34 | 35 | // Compare results 36 | require.Equal(t, tt.args, got, "deserialized args should match original args") 37 | }) 38 | } 39 | } 40 | 41 | func TestGetHeaderResult(t *testing.T) { 42 | testHeader := tu.GetByteFromHexString(t, "010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e36299") 43 | 44 | tests := []struct { 45 | name string 46 | result *wallet.GetHeaderResult 47 | }{ 48 | { 49 | name: "valid header", 50 | result: &wallet.GetHeaderResult{ 51 | Header: testHeader, 52 | }, 53 | }, 54 | } 55 | 56 | for _, tt := range tests { 57 | t.Run(tt.name, func(t *testing.T) { 58 | // Test serialization 59 | data, err := SerializeGetHeaderResult(tt.result) 60 | require.NoError(t, err, "serializing GetHeaderResult should not error") 61 | 62 | // Test deserialization 63 | got, err := DeserializeGetHeaderResult(data) 64 | require.NoError(t, err, "deserializing GetHeaderResult should not error") 65 | 66 | // Compare results 67 | require.Equal(t, tt.result, got, "deserialized result should match original result") 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /script/interpreter/config.go: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import "math" 4 | 5 | type config interface { 6 | AfterGenesis() bool 7 | MaxOps() int 8 | MaxStackSize() int 9 | MaxScriptSize() int 10 | MaxScriptElementSize() int 11 | MaxScriptNumberLength() int 12 | MaxPubKeysPerMultiSig() int 13 | } 14 | 15 | // Limits applied to transactions before genesis 16 | const ( 17 | MaxOpsBeforeGenesis = 500 18 | MaxStackSizeBeforeGenesis = 1000 19 | MaxScriptSizeBeforeGenesis = 10000 20 | MaxScriptElementSizeBeforeGenesis = 520 21 | MaxScriptNumberLengthBeforeGenesis = 4 22 | MaxPubKeysPerMultiSigBeforeGenesis = 20 23 | ) 24 | 25 | type beforeGenesisConfig struct{} 26 | type afterGenesisConfig struct{} 27 | 28 | func (a *afterGenesisConfig) AfterGenesis() bool { 29 | return true 30 | } 31 | 32 | func (b *beforeGenesisConfig) AfterGenesis() bool { 33 | return false 34 | } 35 | 36 | func (a *afterGenesisConfig) MaxStackSize() int { 37 | return math.MaxInt32 38 | } 39 | 40 | func (b *beforeGenesisConfig) MaxStackSize() int { 41 | return MaxStackSizeBeforeGenesis 42 | } 43 | 44 | func (a *afterGenesisConfig) MaxScriptSize() int { 45 | return math.MaxInt32 46 | } 47 | 48 | func (b *beforeGenesisConfig) MaxScriptSize() int { 49 | return MaxScriptSizeBeforeGenesis 50 | } 51 | 52 | func (a *afterGenesisConfig) MaxScriptElementSize() int { 53 | return math.MaxInt32 54 | } 55 | 56 | func (b *beforeGenesisConfig) MaxScriptElementSize() int { 57 | return MaxScriptElementSizeBeforeGenesis 58 | } 59 | 60 | func (a *afterGenesisConfig) MaxScriptNumberLength() int { 61 | return 750 * 1000 // 750 * 1Kb 62 | } 63 | 64 | func (b *beforeGenesisConfig) MaxScriptNumberLength() int { 65 | return MaxScriptNumberLengthBeforeGenesis 66 | } 67 | 68 | func (a *afterGenesisConfig) MaxOps() int { 69 | return math.MaxInt32 70 | } 71 | 72 | func (b *beforeGenesisConfig) MaxOps() int { 73 | return MaxOpsBeforeGenesis 74 | } 75 | 76 | func (a *afterGenesisConfig) MaxPubKeysPerMultiSig() int { 77 | return math.MaxInt32 78 | } 79 | 80 | func (b *beforeGenesisConfig) MaxPubKeysPerMultiSig() int { 81 | return MaxPubKeysPerMultiSigBeforeGenesis 82 | } 83 | -------------------------------------------------------------------------------- /transaction/broadcaster/taal.go: -------------------------------------------------------------------------------- 1 | package broadcaster 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "net/http" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/bsv-blockchain/go-sdk/transaction" 12 | "github.com/bsv-blockchain/go-sdk/util" 13 | ) 14 | 15 | type TAALResponse struct { 16 | Txid string `json:"txid"` 17 | Status uint32 `json:"status"` 18 | Err string `json:"error"` 19 | } 20 | 21 | type TAALBroadcast struct { 22 | ApiKey string 23 | Client util.HTTPClient 24 | } 25 | 26 | func (b *TAALBroadcast) Broadcast(t *transaction.Transaction) (*transaction.BroadcastSuccess, *transaction.BroadcastFailure) { 27 | return b.BroadcastCtx(context.Background(), t) 28 | } 29 | 30 | func (b *TAALBroadcast) BroadcastCtx(ctx context.Context, t *transaction.Transaction) ( 31 | *transaction.BroadcastSuccess, 32 | *transaction.BroadcastFailure, 33 | ) { 34 | buf := bytes.NewBuffer(t.Bytes()) 35 | url := "https://api.taal.com/api/v1/broadcast" 36 | 37 | req, err := http.NewRequestWithContext( 38 | ctx, 39 | "POST", 40 | url, 41 | buf, 42 | ) 43 | if err != nil { 44 | return nil, &transaction.BroadcastFailure{ 45 | Code: "500", 46 | Description: err.Error(), 47 | } 48 | } 49 | req.Header.Set("Content-Type", "application/octet-stream") 50 | if b.ApiKey != "" { 51 | req.Header.Set("Authorization", b.ApiKey) 52 | } 53 | if resp, err := b.Client.Do(req); err != nil { 54 | return nil, &transaction.BroadcastFailure{ 55 | Code: "500", 56 | Description: err.Error(), 57 | } 58 | } else { 59 | defer resp.Body.Close() 60 | var taalResp TAALResponse 61 | if err := json.NewDecoder(resp.Body).Decode(&taalResp); err != nil { 62 | return nil, &transaction.BroadcastFailure{ 63 | Code: strconv.Itoa(resp.StatusCode), 64 | Description: "unknown error", 65 | } 66 | } else if resp.StatusCode != 200 && !strings.Contains(taalResp.Err, "txn-already-known") { 67 | return nil, &transaction.BroadcastFailure{ 68 | Code: strconv.Itoa(resp.StatusCode), 69 | Description: taalResp.Err, 70 | } 71 | } else { 72 | return &transaction.BroadcastSuccess{ 73 | Txid: t.TxID().String(), 74 | }, nil 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /script/addressvalidation.go: -------------------------------------------------------------------------------- 1 | package script 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | 8 | crypto "github.com/bsv-blockchain/go-sdk/primitives/hash" 9 | ) 10 | 11 | type a25 [25]byte 12 | 13 | func (a *a25) embeddedChecksum() (c [4]byte) { 14 | copy(c[:], a[21:]) 15 | return 16 | } 17 | 18 | // computeChecksum returns a four byte checksum computed from the first 21 19 | // bytes of the address. The embedded checksum is not updated. 20 | func (a *a25) computeChecksum() (c [4]byte) { 21 | copy(c[:], crypto.Sha256d(a[:21])) 22 | return 23 | } 24 | 25 | // Tmpl and set58 are adapted from the C solution. 26 | // Go has big integers but this technique seems better. 27 | var tmpl = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") 28 | 29 | // set58 takes a base58 encoded address and decodes it into the receiver. 30 | // Errors are returned if the argument is not valid base58 or if the decoded 31 | // value does not fit in the 25 byte address. The address is not otherwise 32 | // checked for validity. 33 | func (a *a25) set58(s []byte) error { 34 | for _, s1 := range s { 35 | c := bytes.IndexByte(tmpl, s1) 36 | if c < 0 { 37 | return ErrEncodingBadChar 38 | } 39 | for j := 24; j >= 0; j-- { 40 | c += 58 * int(a[j]) 41 | a[j] = byte(c % 256) 42 | c /= 256 43 | } 44 | if c > 0 { 45 | return ErrEncodingTooLong 46 | } 47 | } 48 | return nil 49 | } 50 | 51 | // ValidateAddress checks if an address string is a valid BitCoin address (ex. P2PKH, BIP276). 52 | // Checks both mainnet and testnet. 53 | func ValidateAddress(address string) (bool, error) { 54 | if strings.HasPrefix(address, "bitcoin-script:") { 55 | if _, err := DecodeBIP276(address); err != nil { 56 | return false, fmt.Errorf("bitcoin-script invalid [%w]", err) 57 | } 58 | return true, nil 59 | } 60 | 61 | return validA58([]byte(address)) 62 | } 63 | 64 | func validA58(a58 []byte) (bool, error) { 65 | var a a25 66 | if err := a.set58(a58); err != nil { 67 | return false, err 68 | } 69 | if a[0] != 0 && a[0] != 0x6f { 70 | return false, ErrEncodingInvalidVersion 71 | } 72 | 73 | if a.embeddedChecksum() != a.computeChecksum() { 74 | return false, ErrEncodingChecksumFailed 75 | } 76 | 77 | return true, nil 78 | } 79 | -------------------------------------------------------------------------------- /wallet/serializer/decrypt.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/bsv-blockchain/go-sdk/util" 7 | "github.com/bsv-blockchain/go-sdk/wallet" 8 | ) 9 | 10 | func SerializeDecryptArgs(args *wallet.DecryptArgs) ([]byte, error) { 11 | w := util.NewWriter() 12 | 13 | // Encode key related params (protocol, key, counterparty, privileged) 14 | params := KeyRelatedParams{ 15 | ProtocolID: args.ProtocolID, 16 | KeyID: args.KeyID, 17 | Counterparty: args.Counterparty, 18 | Privileged: &args.Privileged, 19 | PrivilegedReason: args.PrivilegedReason, 20 | } 21 | keyParams, err := encodeKeyRelatedParams(params) 22 | if err != nil { 23 | return nil, fmt.Errorf("error encoding key params: %w", err) 24 | } 25 | w.WriteBytes(keyParams) 26 | 27 | // Write ciphertext length + bytes 28 | w.WriteVarInt(uint64(len(args.Ciphertext))) 29 | w.WriteBytes(args.Ciphertext) 30 | 31 | // Write seekPermission flag 32 | w.WriteOptionalBool(&args.SeekPermission) 33 | 34 | return w.Buf, nil 35 | } 36 | 37 | func DeserializeDecryptArgs(data []byte) (*wallet.DecryptArgs, error) { 38 | r := util.NewReaderHoldError(data) 39 | args := &wallet.DecryptArgs{} 40 | 41 | // Decode key related params 42 | params, err := decodeKeyRelatedParams(r) 43 | if err != nil { 44 | return nil, fmt.Errorf("error decoding key params: %w", err) 45 | } 46 | args.ProtocolID = params.ProtocolID 47 | args.KeyID = params.KeyID 48 | args.Counterparty = params.Counterparty 49 | args.Privileged = util.PtrToBool(params.Privileged) 50 | args.PrivilegedReason = params.PrivilegedReason 51 | 52 | // Read ciphertext 53 | ciphertextLen := r.ReadVarInt() 54 | args.Ciphertext = r.ReadBytes(int(ciphertextLen)) 55 | 56 | // Read seekPermission 57 | args.SeekPermission = util.PtrToBool(r.ReadOptionalBool()) 58 | 59 | r.CheckComplete() 60 | if r.Err != nil { 61 | return nil, fmt.Errorf("error decrypting args: %w", r.Err) 62 | } 63 | 64 | return args, nil 65 | } 66 | 67 | func SerializeDecryptResult(result *wallet.DecryptResult) ([]byte, error) { 68 | return result.Plaintext, nil 69 | } 70 | 71 | func DeserializeDecryptResult(data []byte) (*wallet.DecryptResult, error) { 72 | return &wallet.DecryptResult{ 73 | Plaintext: data, 74 | }, nil 75 | } 76 | -------------------------------------------------------------------------------- /wallet/serializer/encrypt.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/bsv-blockchain/go-sdk/util" 7 | "github.com/bsv-blockchain/go-sdk/wallet" 8 | ) 9 | 10 | func SerializeEncryptArgs(args *wallet.EncryptArgs) ([]byte, error) { 11 | w := util.NewWriter() 12 | 13 | // Encode key related params (protocol, key, counterparty, privileged) 14 | params := KeyRelatedParams{ 15 | ProtocolID: args.ProtocolID, 16 | KeyID: args.KeyID, 17 | Counterparty: args.Counterparty, 18 | Privileged: &args.Privileged, 19 | PrivilegedReason: args.PrivilegedReason, 20 | } 21 | paramBytes, err := encodeKeyRelatedParams(params) 22 | if err != nil { 23 | return nil, fmt.Errorf("error encoding key params: %w", err) 24 | } 25 | w.WriteBytes(paramBytes) 26 | 27 | // Write plaintext length and data 28 | w.WriteVarInt(uint64(len(args.Plaintext))) 29 | w.WriteBytes(args.Plaintext) 30 | 31 | // Write seekPermission flag 32 | w.WriteOptionalBool(&args.SeekPermission) 33 | 34 | return w.Buf, nil 35 | } 36 | 37 | func DeserializeEncryptArgs(data []byte) (*wallet.EncryptArgs, error) { 38 | r := util.NewReaderHoldError(data) 39 | args := &wallet.EncryptArgs{} 40 | 41 | // Decode key related params 42 | params, err := decodeKeyRelatedParams(r) 43 | if err != nil { 44 | return nil, fmt.Errorf("error decoding key params: %w", err) 45 | } 46 | args.ProtocolID = params.ProtocolID 47 | args.KeyID = params.KeyID 48 | args.Counterparty = params.Counterparty 49 | args.Privileged = util.PtrToBool(params.Privileged) 50 | args.PrivilegedReason = params.PrivilegedReason 51 | 52 | // Read plaintext 53 | plaintextLen := r.ReadVarInt() 54 | args.Plaintext = r.ReadBytes(int(plaintextLen)) 55 | 56 | // Read seekPermission 57 | args.SeekPermission = util.PtrToBool(r.ReadOptionalBool()) 58 | 59 | r.CheckComplete() 60 | if r.Err != nil { 61 | return nil, fmt.Errorf("error decrypting encrypt args: %w", r.Err) 62 | } 63 | 64 | return args, nil 65 | } 66 | 67 | func SerializeEncryptResult(result *wallet.EncryptResult) ([]byte, error) { 68 | return result.Ciphertext, nil 69 | } 70 | 71 | func DeserializeEncryptResult(data []byte) (*wallet.EncryptResult, error) { 72 | return &wallet.EncryptResult{ 73 | Ciphertext: data, 74 | }, nil 75 | } 76 | -------------------------------------------------------------------------------- /primitives/ec/precompute.go: -------------------------------------------------------------------------------- 1 | package primitives 2 | 3 | // Copyright 2015 The btcsuite developers 4 | // Use of this source code is governed by an ISC 5 | // license that can be found in the LICENSE file. 6 | 7 | import ( 8 | "compress/zlib" 9 | "encoding/base64" 10 | "encoding/binary" 11 | "io" 12 | "strings" 13 | ) 14 | 15 | //go:generate go run -tags gensecp256k1 genprecomps.go 16 | 17 | // loadS256BytePoints decompresses and deserializes the pre-computed byte points 18 | // used to accelerate scalar base multiplication for the secp256k1 curve. This 19 | // approach is used since it allows the compile to use significantly less ram 20 | // and be performed much faster than it is with hard-coding the final in-memory 21 | // data structure. At the same time, it is quite fast to generate the in-memory 22 | // data structure at init time with this approach versus computing the table. 23 | func loadS256BytePoints() error { 24 | // There will be no byte points to load when generating them. 25 | bp := secp256k1BytePoints 26 | if len(bp) == 0 { 27 | return nil 28 | } 29 | 30 | // Decompress the pre-computed table used to accelerate scalar base 31 | // multiplication. 32 | decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(bp)) 33 | r, err := zlib.NewReader(decoder) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | serialized, err := io.ReadAll(r) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | // Deserialize the precomputed byte points and set the curve to them. 44 | offset := 0 45 | var bytePoints [32][256][3]fieldVal 46 | 47 | for byteNum := 0; byteNum < 32; byteNum++ { 48 | // All points in this window. 49 | for i := 0; i < 256; i++ { 50 | px := &bytePoints[byteNum][i][0] 51 | py := &bytePoints[byteNum][i][1] 52 | pz := &bytePoints[byteNum][i][2] 53 | 54 | for i := 0; i < 10; i++ { 55 | px.n[i] = binary.LittleEndian.Uint32(serialized[offset:]) 56 | offset += 4 57 | } 58 | 59 | for i := 0; i < 10; i++ { 60 | py.n[i] = binary.LittleEndian.Uint32(serialized[offset:]) 61 | offset += 4 62 | } 63 | 64 | for i := 0; i < 10; i++ { 65 | pz.n[i] = binary.LittleEndian.Uint32(serialized[offset:]) 66 | offset += 4 67 | } 68 | } 69 | } 70 | 71 | secp256k1.bytePoints = &bytePoints 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /primitives/aescbc/cbc.go: -------------------------------------------------------------------------------- 1 | package primitives 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "errors" 8 | ) 9 | 10 | // AESCBCEncrypt encrypts data using AES in CBC mode with an IV 11 | func AESCBCEncrypt(data, key, iv []byte, concatIv bool) ([]byte, error) { 12 | block, err := aes.NewCipher(key) 13 | if err != nil { 14 | return nil, err 15 | } 16 | data = PKCS7Padd(data, block.BlockSize()) 17 | blockModel := cipher.NewCBCEncrypter(block, iv) 18 | cipherText := make([]byte, len(data)) 19 | blockModel.CryptBlocks(cipherText, data) 20 | if concatIv { 21 | cipherText = append(iv, cipherText...) 22 | } 23 | return cipherText, nil 24 | } 25 | 26 | // AESCBCDecrypt decrypts data using AES in CBC mode with an IV 27 | func AESCBCDecrypt(data, key, iv []byte) ([]byte, error) { 28 | block, err := aes.NewCipher(key) 29 | if err != nil { 30 | return nil, err 31 | } 32 | blockModel := cipher.NewCBCDecrypter(block, iv) 33 | plantText := make([]byte, len(data)) 34 | blockModel.CryptBlocks(plantText, data) 35 | plantText, err = PKCS7Unpad(plantText, block.BlockSize()) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return plantText, nil 40 | } 41 | 42 | func PKCS7Padd(data []byte, blockSize int) []byte { 43 | padding := blockSize - len(data)%blockSize 44 | return append(data, bytes.Repeat([]byte{byte(padding)}, padding)...) 45 | } 46 | 47 | // PKCS7UnPadding removes padding from the plaintext 48 | func PKCS7Unpad(data []byte, blockSize int) ([]byte, error) { 49 | length := len(data) 50 | 51 | // Check if the data length is a multiple of the block size or if it's empty 52 | if length%blockSize != 0 || length == 0 { 53 | return nil, errors.New("invalid padding length") 54 | } 55 | 56 | // Get the padding length from the last byte 57 | padding := int(data[length-1]) 58 | 59 | // Check if the padding length is larger than the block size 60 | if padding > blockSize { 61 | return nil, errors.New("invalid padding byte (large)") 62 | } 63 | 64 | // Check all padding bytes to ensure they are consistent 65 | for _, v := range data[len(data)-padding:] { 66 | if int(v) != padding { 67 | return nil, errors.New("invalid padding byte (inconsistent)") 68 | } 69 | } 70 | 71 | // Return the data without padding 72 | return data[:(length - padding)], nil 73 | } 74 | -------------------------------------------------------------------------------- /wallet/serializer/create_hmac_test.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "github.com/bsv-blockchain/go-sdk/wallet" 5 | "github.com/stretchr/testify/require" 6 | "testing" 7 | ) 8 | 9 | func TestCreateHMACArgs(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | args *wallet.CreateHMACArgs 13 | }{{ 14 | name: "full args", 15 | args: &wallet.CreateHMACArgs{ 16 | EncryptionArgs: wallet.EncryptionArgs{ 17 | ProtocolID: wallet.Protocol{ 18 | SecurityLevel: wallet.SecurityLevelEveryApp, 19 | Protocol: "test-protocol", 20 | }, 21 | KeyID: "test-key", 22 | Counterparty: wallet.Counterparty{Type: wallet.CounterpartyTypeSelf}, 23 | Privileged: true, 24 | PrivilegedReason: "test-reason", 25 | SeekPermission: true, 26 | }, 27 | Data: []byte{1, 2, 3, 4}, 28 | }, 29 | }, { 30 | name: "minimal args", 31 | args: &wallet.CreateHMACArgs{ 32 | EncryptionArgs: wallet.EncryptionArgs{ 33 | ProtocolID: wallet.Protocol{ 34 | SecurityLevel: wallet.SecurityLevelSilent, 35 | Protocol: "minimal", 36 | }, 37 | KeyID: "minimal-key", 38 | }, 39 | Data: []byte{1}, 40 | }, 41 | }, { 42 | name: "empty data", 43 | args: &wallet.CreateHMACArgs{ 44 | EncryptionArgs: wallet.EncryptionArgs{ 45 | ProtocolID: wallet.Protocol{ 46 | SecurityLevel: wallet.SecurityLevelSilent, 47 | Protocol: "empty-data", 48 | }, 49 | KeyID: "empty-key", 50 | }, 51 | Data: []byte{}, 52 | }, 53 | }} 54 | 55 | for _, tt := range tests { 56 | t.Run(tt.name, func(t *testing.T) { 57 | // Test serialization 58 | data, err := SerializeCreateHMACArgs(tt.args) 59 | require.NoError(t, err) 60 | 61 | // Test deserialization 62 | got, err := DeserializeCreateHMACArgs(data) 63 | require.NoError(t, err) 64 | 65 | // Compare results 66 | require.Equal(t, tt.args, got) 67 | }) 68 | } 69 | } 70 | 71 | func TestCreateHMACResult(t *testing.T) { 72 | t.Run("serialize/deserialize", func(t *testing.T) { 73 | result := &wallet.CreateHMACResult{HMAC: [32]byte{1, 2, 3, 4}} 74 | data, err := SerializeCreateHMACResult(result) 75 | require.NoError(t, err) 76 | 77 | got, err := DeserializeCreateHMACResult(data) 78 | require.NoError(t, err) 79 | require.Equal(t, result, got) 80 | }) 81 | } 82 | -------------------------------------------------------------------------------- /docs/examples/registry_register/registry_register.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "testing" 8 | 9 | "github.com/bsv-blockchain/go-sdk/registry" 10 | "github.com/bsv-blockchain/go-sdk/wallet" 11 | ) 12 | 13 | // This example shows how to use the RegistryClient to register a basket definition 14 | // In a real application, you would use a wallet implementation to sign and broadcast 15 | // transactions. Here we use a mock wallet for demonstration purposes. 16 | func main() { 17 | // Create a test instance 18 | test := &testing.T{} 19 | 20 | // Create a mock wallet (for example purposes only) 21 | mockWallet := registry.NewMockRegistry(test) 22 | 23 | // Set up mock response 24 | mockWallet.CreateActionResultToReturn = &wallet.CreateActionResult{ 25 | Tx: []byte("mock_transaction_beef"), 26 | } 27 | 28 | // Create a context 29 | ctx := context.Background() 30 | 31 | // Create a registry client with the mock wallet 32 | client := registry.NewRegistryClient(mockWallet, "example-registry-app") 33 | 34 | // Create a new basket definition 35 | basketDef := ®istry.BasketDefinitionData{ 36 | DefinitionType: registry.DefinitionTypeBasket, 37 | BasketID: "example-basket-id", 38 | Name: "Example Basket", 39 | IconURL: "https://example.com/icon.png", 40 | Description: "An example basket definition for the BSV registry", 41 | DocumentationURL: "https://example.com/docs", 42 | } 43 | 44 | // Register the definition on-chain 45 | fmt.Println("Registering basket definition...") 46 | result, err := client.RegisterDefinition(ctx, basketDef) 47 | if err != nil { 48 | log.Fatalf("Failed to register definition: %v", err) 49 | } 50 | 51 | // Print the result 52 | fmt.Printf("Successfully registered basket definition!\n") 53 | if result.Success != nil { 54 | fmt.Printf("Success: %+v\n", result.Success) 55 | } else if result.Failure != nil { 56 | fmt.Printf("Failure: %+v\n", result.Failure) 57 | } 58 | fmt.Printf("Basket ID: %s\n", basketDef.BasketID) 59 | fmt.Printf("Name: %s\n", basketDef.Name) 60 | 61 | // NOTE: In a real application, you would: 62 | // 1. Create a proper wallet implementation 63 | // 2. Handle the broadcast response appropriately 64 | // 3. Store the transaction information for future reference 65 | } 66 | --------------------------------------------------------------------------------