carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "crypto/ed25519"
5 "encoding/base64"
6 "encoding/json"
7 "fmt"
8)
9
10// PointerAccount reads the account a channel pointer names WITHOUT verifying
11// it — "" for a pointer from before the binding existed (discussion #220).
12// For planning only: `accounts migrate` asks it whether a pointer can travel
13// as bytes or has to be re-signed for its new account, and it trusts nothing
14// it learns here. Whatever it then writes is verified like any pointer.
15func PointerAccount(wrapper []byte) (string, error) {
16 var sw signedWrapper
17 if err := json.Unmarshal(wrapper, &sw); err != nil {
18 return "", fmt.Errorf("pointer wrapper: %v", err)
19 }
20 payload, err := base64.StdEncoding.DecodeString(sw.Payload)
21 if err != nil {
22 return "", fmt.Errorf("pointer payload: %v", err)
23 }
24 var p ChannelPointer
25 if err := json.Unmarshal(payload, &p); err != nil {
26 return "", fmt.Errorf("pointer payload: %v", err)
27 }
28 return p.Account, nil
29}
30
31// RebindPointer is how a channel pointer moves between accounts. A pointer
32// names the account it was signed for, so a byte copy into another account's
33// prefix no longer verifies there — which is the point of the binding, and
34// what `accounts migrate` would otherwise trip over. The pointer is verified
35// under fromAcct with root's public half, then re-signed by root for toAcct
36// with every other field kept, PromotedAt included.
37//
38// Deterministic on purpose: Ed25519 signatures are, and nothing is
39// re-stamped, so the same source bytes always rebind to the same bytes. The
40// migration compares destination bytes against this output to decide whether
41// a pointer has already been copied, and a fresh timestamp would make every
42// re-run look like a disagreement.
43//
44// A pointer that names no account (written before the binding) or already
45// names toAcct is returned as is: it verifies at the destination unchanged.
46func RebindPointer(root ed25519.PrivateKey, wrapper []byte, fromAcct, toAcct, app, channel string) ([]byte, error) {
47 if !ValidAccount(toAcct) {
48 return nil, fmt.Errorf("rebind: invalid account %q", toAcct)
49 }
50 p, err := verifyPointer(root.Public().(ed25519.PublicKey), wrapper, fromAcct, app, channel)
51 if err != nil {
52 return nil, fmt.Errorf("rebind %s/%s: %w", app, channel, err)
53 }
54 if p.Account == "" || p.Account == toAcct {
55 return wrapper, nil
56 }
57 p.Account = toAcct
58 if err := validatePointer(p, toAcct, app, channel); err != nil {
59 return nil, err
60 }
61 payload, err := json.MarshalIndent(p, "", " ")
62 if err != nil {
63 return nil, err
64 }
65 b64 := base64.StdEncoding.EncodeToString(payload)
66 return json.MarshalIndent(signedWrapper{
67 Payload: b64,
68 Sig: base64.StdEncoding.EncodeToString(ed25519.Sign(root, []byte(b64))),
69 }, "", " ")
70}
71