From cb47d0c8251051337fa8b16377b40d7d68ac0410 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 15 Jan 2021 14:52:00 -0800 Subject: [PATCH] amp/sharer: add recursive sharing logic --- amp/child.go | 7 +++ amp/sharer.go | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 amp/sharer.go diff --git a/amp/child.go b/amp/child.go index 3ba77fae..32bbe298 100644 --- a/amp/child.go +++ b/amp/child.go @@ -12,6 +12,13 @@ import ( // be recovered by XORing all n shares together. type Share [32]byte +// Xor stores the byte-wise xor of shares x and y in z. +func (z *Share) Xor(x, y *Share) { + for i := range z { + z[i] = x[i] ^ y[i] + } +} + // ChildDesc contains the information necessary to derive a child hash/preimage // pair that is attached to a particular HTLC. This information will be known by // both the sender and receiver in the process of fulfilling an AMP payment. diff --git a/amp/sharer.go b/amp/sharer.go new file mode 100644 index 00000000..de27550c --- /dev/null +++ b/amp/sharer.go @@ -0,0 +1,152 @@ +package amp + +import ( + "crypto/rand" +) + +// Sharer facilitates dynamic splitting of a root share value and derivation of +// child preimage and hashes for individual HTLCs in an AMP payment. A sharer +// represents a specific node in an abstract binary tree that can generate up to +// 2^32-1 unique child preimage-hash pairs for the same share value. A node can +// also be split into it's left and right child in the tree. The Sharer +// guarantees that the share value of the left and right child XOR to the share +// value of the parent. This allows larger HTLCs to split into smaller +// subpayments, while ensuring that the reconstructed secret will exactly match +// the root seed. +type Sharer interface { + // Root returns the root share of the derivation tree. This is the value + // that will be reconstructed when combining the set of all child + // shares. + Root() Share + + // Child derives a child preimage and child hash given a 32-bit index. + // Passing a different index will generate a unique preimage-hash pair + // with high probability, allowing the payment hash carried on HTLCs to + // be refreshed without needing to modify the share value. This would + // typically be used when an partial payment needs to be retried if it + // encounters routine network failures. + Child(index uint32) *Child + + // Split returns a Sharer for the left and right child of the parent + // Sharer. XORing the share values of both sharers always yields the + // share value of the parent. The sender should use this to recursively + // divide payments that are too large into smaller subpayments, knowing + // that the shares of all nodes descending from the parent will XOR to + // the parent's share. + Split() (Sharer, Sharer, error) +} + +// SeedSharer orchestrates the sharing of the root AMP seed along multiple +// paths. It also supports derivation of the child payment hashes that get +// attached to HTLCs, and the child preimages used by the receiver to settle +// individual HTLCs in the set. +type SeedSharer struct { + root Share + curr Share +} + +// NewSeedSharer generates a new SeedSharer instance with a seed drawn at +// random. +func NewSeedSharer() (*SeedSharer, error) { + var root Share + if _, err := rand.Read(root[:]); err != nil { + return nil, err + } + + return SeedSharerFromRoot(&root), nil +} + +// SeedSharerFromRoot instantiates a SeedSharer with an externally provided +// seed. +func SeedSharerFromRoot(root *Share) *SeedSharer { + return initSeedSharer(root, root) +} + +func initSeedSharer(root, curr *Share) *SeedSharer { + return &SeedSharer{ + root: *root, + curr: *curr, + } +} + +// Seed returns the sharer's seed, the primary source of entropy for deriving +// shares of the root. +func (s *SeedSharer) Root() Share { + return s.root +} + +// Split constructs two child Sharers whose shares sum to the parent Sharer. +// This allows an HTLC whose payment amount could not be routed to be +// recursively split into smaller subpayments. After splitting a sharer the +// parent share should no longer be used, and the caller should use the Child +// method on each to derive preimage/hash pairs for the HTLCs. +func (s *SeedSharer) Split() (Sharer, Sharer, error) { + shareLeft, shareRight, err := split(&s.curr) + if err != nil { + return nil, nil, err + } + + left := initSeedSharer(&s.root, &shareLeft) + right := initSeedSharer(&s.root, &shareRight) + + return left, right, nil +} + +// Child derives a preimage/hash pair to be used for an AMP HTLC. +// All children of s will use the same underlying share, but have unique +// preimage and hash. This can be used to rerandomize the preimage/hash pair for +// a given HTLC if a new route is needed. +func (s *SeedSharer) Child(index uint32) *Child { + desc := ChildDesc{ + Share: s.curr, + Index: index, + } + + return DeriveChild(s.root, desc) +} + +// ReconstructChildren derives the set of children hashes and preimages from the +// provided descriptors. The shares from each child descriptor are first used to +// compute the root, afterwards the child hashes and preimages are +// deterministically computed. For child descriptor at index i in the input, +// it's derived child will occupy index i of the returned children. +func ReconstructChildren(descs ...ChildDesc) []*Child { + // Recompute the root by XORing the provided shares. + var root Share + for _, desc := range descs { + root.Xor(&root, &desc.Share) + } + + // With the root computed, derive the child hashes and preimages from + // the child descriptors. + children := make([]*Child, len(descs)) + for i, desc := range descs { + children[i] = DeriveChild(root, desc) + } + + return children +} + +// split splits a share into two random values, that when XOR'd reproduce the +// original share. Given a share s, the two shares are derived as: +// left <-$- random +// right = parent ^ left. +// +// When reconstructed, we have that: +// left ^ right = left ^ parent ^ left +// = parent. +func split(parent *Share) (Share, Share, error) { + // Generate a random share for the left child. + var left Share + if _, err := rand.Read(left[:]); err != nil { + return Share{}, Share{}, err + } + + // Compute right = parent ^ left. + var right Share + right.Xor(parent, &left) + + return left, right, nil +} + +var _ Sharer = (*SeedSharer)(nil)