webledgers-teller 0.2.0

A teller for Web Ledgers on txbt4: did:nostr accounts, a ledger identified by the hash of its genesis, one taproot deposit address per account by additive tweak of the operator's key, signed join/withdraw/transfer requests (kind 3700) applied once, and payouts signed per input with each deposit's derived secret. Pure, no I/O. A port of solidpayorg/teller by Melvin Carvalho.
Documentation
# webledgers-teller

> Part of [sidestr-rs]https://github.com/DreamLab-AI/sidestr-rs. Rust port of Melvin Carvalho's teller for Web Ledgers, AGPL-3.0-or-later: did:nostr accounts with balances on a public ledger, paid in and out on txbt4.

A teller for [Web Ledgers](https://webledgers.org/), in Rust: accounts are
did:nostr keys, balances a Web Ledger, deposits one taproot address per
account, withdrawals and transfers signed requests. One operator holds the
deposits and writes the ledger; everyone else reads it and signs requests.
The proposal is
[solidpayorg/webledgers#7](https://github.com/solidpayorg/webledgers/issues/7).

```toml
[dependencies]
webledgers-teller = "0.2"
```

- **The ledger** is a Web Ledger JSON document with a genesis (operator,
  name, currency, confirmations) fixed at creation; its hash, the SHA-256 of
  the genesis's JCS, is its identity. The operator publishes it as a Nostr
  event of kind 30333 (addressable by `d` = the hash) and replaces it as
  balances move. A reader accepts only the operator's own signed copy.
  Deposits are credited once by outpoint; transfers and withdrawals once by
  request id; amounts are whole satoshis, written as strings.
- **Deposits.** Each account's deposit address is the operator's point plus
  `tagged("webledgers/deposit", ledgerHash ‖ account ‖ nonce)·G`, added to
  the full point and never its even-y lift (sidestr/spec `keys.mjs`). The
  operator is read as its did (the `02` point of its x) whatever parity it is
  given as, and its secret is normalised to that same point. Anyone with the
  operator's did and the ledger's hash recomputes the address; only the
  operator can spend it.
- **Requests** are Nostr events of kind 3700 signed by the account's key:
  `join` (be watched), `withdraw` (an amount to an address), `transfer` (an
  amount to an account).
- **Payouts** spend the deposits: coins largest first, change of at least 330
  sat back to the operator's own deposit address, less folded into the fee;
  each input signed with its own derived secret under the chain's sighash
  rules (Knots' unified sighash, `0x21`, on txbt4), every input checked, and
  the fee checked against the signed size. The size estimate is 58 vB per
  key-path input, the teller's fix for a payout once refused as
  "min relay fee not met, 154 < 155".

```rust
use bitcoin::secp256k1::SecretKey;
use webledgers_teller::*;

let operator = SecretKey::from_slice(&[0x11; 32])?;
let op_did = format!("did:nostr:{}", pubkey_hex(&operator));
let mut ledger = new_ledger(&LedgerParams::new(&op_did, "Table 7", now))?;

let alice = "did:nostr:…";
let deposit = deposit_address(&op_did, &ledger.hash, alice, 0, DEFAULT_HRP)?;   // tb1p…
credit(&mut ledger, &Credit { account: alice.into(), txid, vout, value, height }, now)?;

let req = parse_request(&event_from_a_relay, &ledger.hash)?;                    // kind 3700, verified
let plan = plan_payout(&PayoutParams { coins: &coins, amount, rate: 1, to_script: &to, change_script: &change.script })?;
let paid = sign_payout(&plan, &operator, TXBT4_RULES)?;                           // broadcast paid.hex
debit(&mut ledger, &Debit { id: req.id, account: req.account, amount, to: to_address, txid: paid.txid }, now)?;
let ev = ledger_event(&operator, &ledger, now)?;                                  // publish (kind 30333)
```

The crate is pure: no I/O, no clock, no randomness. The caller passes the
time, request ids, the coins the chain holds and the sighash rules, and
publishes and broadcasts what comes back. Signatures use zero BIP 340
auxiliary randomness. It needs `std`, as `sidestr-core` and `sidestr-nostr`
do.

What it is not: not private (anyone who knows the operator's point can link
a ledger's deposit addresses), not hardware-wallet signable (a plain additive
tweak), and custodial (the operator can refuse a withdrawal). It is
checkable: every deposit traces to an account from public data, every change
is a signed request or a deposit seen on-chain. txbt4 coins are test coins.

## Attribution

This crate is a port of **teller** by Melvin Carvalho
([github.com/solidpayorg/teller](https://github.com/solidpayorg/teller),
AGPL-3.0-or-later), from commit `7c00ceac4dc37e0526eccd5ae62e1680ac88ec2a`
(`lib/teller.mjs`, `test/teller-test.mjs`, and the ledger event in
`teller.js`). Two parts come from the libraries the teller loads, by the same
author and under the AGPL:

- the key rule (`normalize`, `basePoint`, `tweakPoint`, `tweakSecret`,
  `taggedScalar`, …) from `siding/lib/keys.mjs` in
  [sidestr/spec]https://github.com/sidestr/spec at `bd1d692` (through
  `e8deb63161c7459ed39c01d2ca9fda3d860b65b6`), with its known answers
  `siding/test/keys-vectors.json` vendored as `tests/vectors/keys-vectors.json`;
- the key-path sighash and the script check (`siding/lib/txsign.mjs`, the
  kernel of [bitcoin-desktop/schema]https://github.com/bitcoin-desktop/schema),
  through [`sidestr-core`]https://crates.io/crates/sidestr-core.

Every function names its original.

## What changed in the port

- The key arithmetic is libsecp256k1's through `bitcoin::secp256k1`; the
  sighash and script check are `sidestr-core`'s; the Nostr event and its
  verification are `sidestr-nostr`'s. Nothing cryptographic is hand-rolled.
- Time, request ids and the chain's sighash rules are parameters. The teller
  reads the clock, draws ids from `crypto.getRandomValues` and loads the
  chain's engine.
- Fee rates are whole sat/vB. The teller pays at 1 and accepts fractions.
- Amounts given as numbers are `u64`, so negative and fractional numbers
  cannot be passed at all. Amounts read from text are refused in the
  teller's words.
- The ledger event (kind 30333), which the teller's page writes and reads
  inline, is here as `ledger_template`, `ledger_event`, `read_ledger_event`
  and `latest_ledger`.
- JCS covers the plain JSON these documents are. Integers are exact; a
  number with a fraction is outside what the teller writes.

## Tests

`cargo test` runs the teller's 24 checks, named as the teller names them
(`tests/teller.rs`), the `keys-vectors.json` cases, and the edges of each
refusal. With `TELLER` (a teller checkout), `SCHEMA`, `BLAKETESTNODE` and
`SIDESTR_LIB` (or `SIDESTR_SIDING`) set, `tests/oracle.rs` also runs the
teller's own suite against those checkouts and cross-checks this crate
against `lib/teller.mjs` through `tests/xcheck-teller.mjs`. The cross-check
covers JCS, accounts, amounts, ledgers and their hashes, a script of credits,
transfers and debits, deposit addresses for every form of operator key,
secrets, watch lists, payout plans with their unsigned transactions, payouts
signed on each side and checked by the other, and requests signed on each
side and read by the other.

## Licence

AGPL-3.0-or-later, as the teller is. See `LICENSE`.