/- *ring :: ring signatures over the edwards curve :: |% :: +raw is the raw internal ring signature implementation. +raw does not deal :: with urbit ship identities or urbit nouns and is low level. It only deals :: with ed25519 keys and message digests. :: :: This raw interface is vaguely modeled on the haskell aos-signature package, :: but is written in terms of ed25519 primitives instead of general ECC and :: changes how linkage tags are computed so that how linkage occurs is a :: client decision instead of hard coding the set of public keys as the :: linkage scope. :: ++ raw |% :: +generate-public-linkage: generate public linkage information :: ++ generate-public-linkage |= link-scope=@ ^- [data=@ h=@udpoint] :: =/ data=@ (mod link-scope l:ed:crypto) =/ h=@udpoint (scalarmult-base:ed:crypto data) [data h] :: +generate-linkage: linkage information from scope and private key :: :: data: deterministically picked data point based off scope :: h: h = [data] * g :: y: y = [x] * h ++ generate-linkage |= [link-scope=(unit @) my-private-key=@] ^- (unit [data=@ h=@udpoint y=@udpoint]) :: ?~ link-scope ~ :: =+ [data=@ h=@udpoint]=(generate-public-linkage u.link-scope) =/ y=@udpoint (scalarmult:ed:crypto my-private-key h) [~ data h y] :: +generate-challenge: generate challenge from a given message :: :: When :link-scope is ~ (ie, we're not building a linked ring signature), :: calculates just the hash of `[message g]`. Otherwise, weaves the :: linkage state into the challenge. :: ++ generate-challenge |= $: :: common to both linked and unlinked message=@ g=@udpoint :: high level universal state :: link-state=(unit [data=@ h=@udpoint y=@udpoint]) :: point to include in challenge when link-state isn't ~ :: h=(unit @udpoint) == ^- @ :: concatenate and reduce our message down to a 512-bit hash =/ concatenated ?~ link-state (shal 96 (can 3 ~[[64 message] [32 g]])) :: %+ shal 192 %+ can 3 :~ [64 message] [32 g] [32 data.u.link-state] [32 y.u.link-state] [32 (need h)] == :: (mod concatenated l:ed:crypto) :: +generate-challenges: generates the full list of challenges :: ++ generate-challenges |= $: link-state=(unit [data=@ h=@udpoint y=@udpoint]) message=@ public-keys=(list @udpoint) ss=(list @) :: prev-k=@u prev-s=@ prev-ch=@ challenges=(list @) == ^- (list @) :: =/ gs=@udpoint %- add-scalarmult-scalarmult-base:ed:crypto :* prev-ch (snag prev-k public-keys) prev-s == :: =/ hs=(unit @udpoint) ?~ link-state ~ :: :- ~ %- add-double-scalarmult:ed:crypto :* prev-s h.u.link-state prev-ch y.u.link-state == :: =/ ch=@ (generate-challenge message gs link-state hs) :: ?~ ss [ch challenges] :: %_ $ prev-k (mod (add prev-k 1) (lent public-keys)) prev-s i.ss prev-ch ch ss t.ss challenges [ch challenges] == :: +scalarmult-h: maybe multiply u by h in linkage :: :: Since linkage tags are optional, we need to be able to just do the math :: in case :linkage is set and fall through otherwise. +scalarmult-h is :: used to generate the (unit point) consumed by +generate-challenge. :: ++ scalarmult-h |= [u=@ linkage=(unit [data=@ h=@udpoint y=@udpoint])] ^- (unit @udpoint) ?~ linkage ~ [~ (scalarmult:ed:crypto u h.u.linkage)] :: +reorder: reorders a list so the ith element is first :: ++ reorder |* [i=@ l=(list)] %+ weld (slag i l) (scag i l) :: +sign: creates a ring signature on an ed25519 curve :: ++ sign |= $: message=@ link-scope=(unit @) :: anonymity-set=(set @udpoint) my-public-key=@udpoint my-private-key=@udscalar :: eny=@uvJ == ^- raw-ring-signature |^ :: k: our public-key's position in :anonymity-list :: =/ k=@u ~| [%couldnt-find my-public-key in=anonymity-list] (need (find [my-public-key ~] anonymity-list)) :: Generate linkage information if given :: =/ linkage=(unit [data=@ h=@udpoint y=@udpoint]) (generate-linkage link-scope my-private-key) :: initialize our random number generator from entropy :: =+ rand=~(. og eny) :: generate the random s values used in the ring :: =^ random-s-values=(list @) rand =| count=@ =| random-s-values=(list @) |- ?: =(count (sub participants 1)) [random-s-values rand] :: =^ v=@ rand (rads:rand l:ed:crypto) $(count (add 1 count), random-s-values [v random-s-values]) :: ?> ?=(^ random-s-values) =/ sk1=@ i.random-s-values =/ sk2-to-prev-sk=(list @) t.random-s-values :: Pick a random :u :: =^ u=@ rand (rads:rand l:ed:crypto) :: Compute challenge at k + 1 :: =/ chk1=@ %- generate-challenge :* message (scalarmult-base:ed:crypto u) linkage (scalarmult-h u linkage) == :: Generate challenges for [ck, ..., c1, c0, ... ck + 2, ck + 1] :: =/ reversed-chk-to-chk1=(list @) %- generate-challenges :* linkage message anonymity-list sk2-to-prev-sk :: (mod (add k 1) participants) sk1 chk1 [chk1 ~] == =/ chk=@ (head reversed-chk-to-chk1) :: Compute s = u - x * c mod n :: =/ sk=@ (~(dif fo l:ed:crypto) u (mul my-private-key chk)) :: =/ ordered-challenges=(list @) (order-challenges k (flop reversed-chk-to-chk1)) :: =/ ordered-ss=(list @) (order-ss k [sk sk1 sk2-to-prev-sk]) =/ ch0 (head ordered-challenges) :: [ch0 ordered-ss ?~(linkage ~ `y.u.linkage)] :: ++ anonymity-list ~(tap in anonymity-set) :: ++ participants (lent anonymity-list) :: ++ order-challenges |= [k=@ ch=(list @)] (reorder (sub participants (add k 1)) ch) :: ++ order-ss |= [k=@ sk-to-prev-sk=(list @)] (reorder (sub participants k) sk-to-prev-sk) -- :: +verify: verify signature :: ++ verify |= $: message=@ link-scope=(unit @) :: anonymity-set=(set @udpoint) signature=raw-ring-signature == ^- ? :: if there's a linkage scope but no tag, fail :: ?: &(?=(^ link-scope) ?=(~ y.signature)) %.n :: if there's no linkage scope but a tag, fail :: ?: &(?=(~ link-scope) ?=(^ y.signature)) %.n :: vice versa. :: :: decompose the signature into [s0 s1 s2....] :: ?> ?=([@ @ *] s.signature) =/ s0=@ i.s.signature =/ s1=@ i.t.s.signature =/ s2-to-end=(list @) t.t.s.signature :: anonymity-list: set of public keys listified in ring order :: =/ anonymity-list=(list @udpoint) ~(tap in anonymity-set) :: participants: length of :anonymity-list :: =/ participants=@u (lent anonymity-list) :: =/ z0p=@udpoint %- add-scalarmult-scalarmult-base:ed:crypto :* ch0.signature (head anonymity-list) s0 == :: generate the linkage using public data, and the y point from the :: signature :: =/ linkage=(unit [data=@ h=@udpoint y=@udpoint]) ?~ link-scope ~ =+ [data=@ h=@udpoint]=(generate-public-linkage u.link-scope) :- ~ [data h (need y.signature)] :: =/ z0pp=(unit @udpoint) ?~ linkage ~ :- ~ %- add-double-scalarmult:ed:crypto :* s0 h.u.linkage ch0.signature y.u.linkage == :: initial challenge :: =/ ch1=@ (generate-challenge message z0p linkage z0pp) :: =/ challenges %- generate-challenges :* linkage message anonymity-list s2-to-end :: (mod 1 participants) s1 ch1 [ch1 ~] == :: =(ch0.signature (head challenges)) -- :: +detail: details about getting keys from Azimuth :: ++ detail |% :: +seed-to-private-key-scalar: keyfile form to scalar we can multiply with :: ++ seed-to-private-key-scalar |= sk=@I ^- @udscalar ?: (gth (met 3 sk) 32) !! =+ h=(shal (rsh [0 3] b:ed:crypto) sk) %+ add (bex (sub b:ed:crypto 2)) (lsh [0 3] (cut 0 [3 (sub b:ed:crypto 5)] h)) :: +get-public-key-from-pass: decode the raw @ public key structure :: ++ get-public-key-from-pass |= a=pass ^- [@ @] =+ [mag=(end 3 a) bod=(rsh 3 a)] ~| %not-crub-pubkey ?> =('b' mag) [cry=(rsh 8 bod) sgn=(end 8 bod)] :: :: ++ get-private-key-from-ring |= a=ring ^- [@ @] =+ [mag=(end 3 a) bod=(rsh 3 a)] ~| %not-crub-seckey ?> =('B' mag) =+ [c=(rsh 8 bod) s=(end 8 bod)] :: todo: do we puck here? [c s] :: +ship-life-to-pubid: fetches public key information from jael :: ++ ship-life-to-pubid |= [our=@p now=@da ship=@p =life] ^- @udpoint :: =/ d=[=^life =pass] =/ scry-path=path :~ %k (scot %p our) (scot %da now) (scot %p ship) (scot %ud life) == .^([^life pass] scry-path) :: we have the deed which has pass, which is several numbers +cat-ed :: together; pull out the keys :: =/ x=[crypt=@ auth=@] (get-public-key-from-pass pass.d) :: `@udpoint`auth.x :: ++ build-signing-participants |= [our=@p now=@da invited=(list @p)] ^- [(set [@p life]) (set @udpoint)] :: =| participants=(set [@p life]) =| keys=(set @udpoint) :: |- ?~ invited [participants keys] :: =/ =life .^(life k+/(scot %p our)/life/(scot %da now)/(scot %p i.invited)) :: ?: =(life 0) $(invited t.invited) :: =/ pubkey=@udpoint (ship-life-to-pubid our now i.invited life) :: =. participants (~(put in participants) [i.invited life]) =. keys (~(put in keys) pubkey) :: $(invited t.invited) :: :: ++ build-verifying-participants |= [our=@p now=@da invited=(list [ship=@p =life])] ^- (set @udpoint) :: =| keys=(set @udpoint) :: |- ?~ invited keys :: =/ pubkey=@udpoint (ship-life-to-pubid our now ship.i.invited life.i.invited) =. keys (~(put in keys) pubkey) :: $(invited t.invited) -- -- :: public interface :: |% :: +sign: ring-signs a message using the current ship :: ++ sign |= $: our=@p now=@da eny=@uvJ :: message=* link-scope=(unit *) anonymity-set=(set @p) == ^- ring-signature :: if our is not in @p, we must be in @p. :: =. anonymity-set (~(put in anonymity-set) our) :: =/ msg-hash=@ (shaz (jam message)) =/ link-hash=(unit @) (bind link-scope |=(a=* (shaz (jam a)))) :: get everyone's public keys :: =/ p=[participants=(set [ship=@p =life]) keys=(set @udpoint)] (build-signing-participants:detail our now ~(tap in anonymity-set)) :: get our ships' current life :: =/ our-life=life .^(life %k /(scot %p our)/life/(scot %da now)/(scot %p our)) :: get our ships' secret keyfile ring :: =/ secret-ring=ring .^(ring %k /(scot %p our)/vein/(scot %da now)/(scot %ud our-life)) :: fetch the encoded auth seed from the ring :: =/ secret-auth-seed=@ +:(get-private-key-from-ring:detail secret-ring) :: get our ships' public key :: =/ public-key=@udpoint (ship-life-to-pubid:detail our now our our-life) :: :- participants.p :- link-scope %- sign:raw :* msg-hash link-hash keys.p public-key (seed-to-private-key-scalar:detail secret-auth-seed) eny == :: +verify: verifies a message against a ring signature :: ++ verify |= [our=@p now=@da message=* =ring-signature] ^- ? :: =/ keys=(set @udpoint) %^ build-verifying-participants:detail our now ~(tap in participants.ring-signature) :: =/ msg-hash=@ (shaz (jam message)) =/ link-hash=(unit @) (bind link-scope.ring-signature |=(a=* (shaz (jam a)))) :: (verify:raw msg-hash link-hash keys raw.ring-signature) --