urbit/pkg/arvo/lib/ring.hoon
2020-12-02 01:00:09 -08:00

478 lines
13 KiB
Plaintext

/- *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)
--