Documentation
¶
Overview ¶
Package pushdrop is the key derivation an application locks its PushDrop outputs under, and the tagged PushDrop those outputs carry: a leading tag field, the application's own fields, and the wallet's signature over them.
Every derivation here is counterparty Anyone with forSelf=true. Measured at go-sdk v1.5.2, that is the only setting under which a reader holding just the identity key can recompute the locking key AND the embedded signature verifies under it, so a reader checks an output without asking its producer anything.
A Derivation is frozen at an application's first mint: the protocol and key id are hashed into every derived key (BRC-43), so changing either orphans every output ever locked under it. The application owns its Derivation and its tags; nothing here names one.
Index ¶
- Variables
- func Anyone() wallet.Counterparty
- type Derivation
- func (d Derivation) ExpectedLockingKey(identity *ec.PublicKey) (*ec.PublicKey, error)
- func (d Derivation) Lock(ctx context.Context, w wallet.Interface, originator string, fields [][]byte, ...) (*script.Script, error)
- func (d Derivation) Unlocker(ctx context.Context, w wallet.Interface, originator string) Template
- func (d Derivation) Validate() error
- type Tagged
- type Template
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrDerivation = errors.New("pushdrop: invalid derivation")
ErrDerivation is what Validate refuses a derivation with.
var ErrNotTagged = errors.New("pushdrop: not a tagged PushDrop")
ErrNotTagged reports a script that is not the tagged PushDrop DecodeTagged was asked for.
Functions ¶
func Anyone ¶
func Anyone() wallet.Counterparty
Anyone is the counterparty every derivation here uses. A zero-value Counterparty is not the same thing (the SDK's GetPublicKey and CreateSignature default it differently) and is never passed.
Types ¶
type Derivation ¶
Derivation is one key under an application's BRC-43 protocol: the protocol (security level and name) and the key id within it, which together give the invoice number "<level>-<name>-<key id>".
func (Derivation) ExpectedLockingKey ¶
ExpectedLockingKey is the reader's side of the derivation: from the anyone root, identity's key under d. It equals what identity's own wallet produces with counterparty Anyone and forSelf=true, which is the key Lock locks to.
func (Derivation) Lock ¶
func (d Derivation) Lock(ctx context.Context, w wallet.Interface, originator string, fields [][]byte, sign bool) (*script.Script, error)
Lock builds a lock-before PushDrop of fields through the wallet, locked to the wallet's key under d with counterparty Anyone and forSelf=true. With sign, the wallet's signature over the fields concatenated follows them as one more field; without it the output carries the fields alone.
The fields slice is freshly allocated on every call because the SDK appends the signature into whatever capacity it is handed, and a reused buffer would carry one output's signature into the next. The field bytes are copied too, so the SDK is never handed a buffer the caller still holds.
Example ¶
A producer locks a signed, tagged PushDrop through its wallet; a reader holding only the producer's identity key recomputes the locking key, decodes the output and checks the field signature. The locking key is the independent vector's objectLockingKeyHex in testdata/vectors/transactions-v1.json.
package main
import (
"context"
"encoding/hex"
"errors"
"fmt"
"github.com/bsv-blockchain/go-sdk/wallet"
"github.com/lightwebinc/bcommon/goldentest"
"github.com/lightwebinc/bcommon/pushdrop"
)
// The derivation and tag the library's test vectors use. They belong to no
// application, are reserved for tests (docs/registry.md), and an application
// supplies its own.
var (
exampleDerivation = pushdrop.Derivation{
Protocol: wallet.Protocol{SecurityLevel: wallet.SecurityLevelEveryApp, Protocol: "vector sample"},
KeyID: "object",
}
exampleTag = []byte{'v', 'x', 0x01}
)
func main() {
ctx := context.Background()
// The fixed TEST key (32 bytes of 0x42). The SDK's proto wallet stands
// in for the producer's BRC-100 wallet: Lock needs only its key calls.
w, err := wallet.NewCompletedProtoWallet(goldentest.FixedKey())
if err != nil {
fmt.Println(err)
return
}
lock, err := exampleDerivation.Lock(ctx, w, "example.com", [][]byte{exampleTag, []byte("hello")}, true)
if err != nil {
fmt.Println(err)
return
}
// The reader's side.
identity := goldentest.FixedKey().PubKey()
want, err := exampleDerivation.ExpectedLockingKey(identity)
if err != nil {
fmt.Println(err)
return
}
out, err := pushdrop.DecodeTagged(lock, exampleTag, 2)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("locking key:", hex.EncodeToString(out.LockingKey.Compressed()))
fmt.Println("derived by the reader:", want.IsEqual(out.LockingKey))
fmt.Printf("fields: %q\n", out.Fields)
fmt.Println("signature verifies:", out.VerifySignature())
// Another tag is not this application's output.
_, err = pushdrop.DecodeTagged(lock, []byte{'v', 'x', 0x02}, 2)
fmt.Println("another tag refused:", errors.Is(err, pushdrop.ErrNotTagged))
}
Output: locking key: 0324d6d6ee75173da1ca8d964d3889792c8911d66c3290a76c367413f5a68e5446 derived by the reader: true fields: ["vx\x01" "hello"] signature verifies: true another tag refused: true
func (Derivation) Unlocker ¶
Unlocker returns the template that spends an output Lock locked under d, through the wallet, signing all outputs. The SDK's pushdrop.Unlocker does not satisfy transaction.UnlockingScriptTemplate on its own (its Sign takes an int and its EstimateLength takes nothing), so Template adapts it.
func (Derivation) Validate ¶
func (d Derivation) Validate() error
Validate applies the BRC-43 rules go-sdk v1.5.2's key deriver applies to a protocol and key id, in the SDK's order, so an application can refuse a derivation that will never produce a key when it builds one rather than at its first mint. The SDK stays the authority: no method here calls Validate, and each derivation still meets the SDK's own check.
The name is measured as the SDK measures it, trimmed and lower-cased, so " Sample " passes here because the SDK derives it as "sample".
Example ¶
Validate applies the SDK's BRC-43 rules when a derivation is built, rather than at the first mint.
package main
import (
"fmt"
"github.com/bsv-blockchain/go-sdk/wallet"
"github.com/lightwebinc/bcommon/pushdrop"
)
// The derivation and tag the library's test vectors use. They belong to no
// application, are reserved for tests (docs/registry.md), and an application
// supplies its own.
var exampleDerivation = pushdrop.Derivation{
Protocol: wallet.Protocol{SecurityLevel: wallet.SecurityLevelEveryApp, Protocol: "vector sample"},
KeyID: "object",
}
func main() {
for _, d := range []pushdrop.Derivation{
exampleDerivation,
{Protocol: wallet.Protocol{SecurityLevel: wallet.SecurityLevelEveryApp, Protocol: "abcd"}, KeyID: "object"},
{Protocol: wallet.Protocol{SecurityLevel: wallet.SecurityLevelEveryApp, Protocol: "vector_sample"}, KeyID: "object"},
{Protocol: wallet.Protocol{SecurityLevel: wallet.SecurityLevelEveryApp, Protocol: "vector sample"}},
} {
fmt.Println(d.Validate())
}
}
Output: <nil> pushdrop: invalid derivation: protocol name "abcd" is under 5 characters pushdrop: invalid derivation: protocol name "vector_sample" has '_'; only letters, digits and spaces pushdrop: invalid derivation: empty key id
type Tagged ¶
type Tagged struct {
// Fields are the fields Lock was given, tag first. The signature is not
// one of them.
Fields [][]byte
// LockingKey is the key the output is locked to.
LockingKey *ec.PublicKey
// Signature is Lock's DER signature over sha256 of Fields concatenated.
Signature []byte
}
Tagged is a decoded signed PushDrop whose first field is a tag.
func DecodeTagged ¶
DecodeTagged parses what Lock writes with sign set: nfields fields, tag included, the first equal to tag, then the signature. Only the lock-before layout decodes, which is the layout Lock writes; a lock-after script from some other producer is refused rather than guessed at. What the fields after the tag must hold is the caller's to check.
func (*Tagged) VerifySignature ¶
VerifySignature checks the embedded signature under the locking key. The wallet signs sha256 of the concatenated fields, so that is what is verified.
type Template ¶
type Template struct {
U *sdkpushdrop.Unlocker
}
Template adapts the SDK's pushdrop.Unlocker to transaction.UnlockingScriptTemplate.
func (Template) EstimateLength ¶
func (t Template) EstimateLength(_ *transaction.Transaction, _ uint32) uint32
EstimateLength is the unlocker's own estimate: one DER signature push.
func (Template) Sign ¶
func (t Template) Sign(tx *transaction.Transaction, inputIndex uint32) (*script.Script, error)
Sign signs input inputIndex of tx through the wallet.