From a42c6038474d13c36207b1332b5db046bea05b63 Mon Sep 17 00:00:00 2001 From: Dylan Cant Date: Sun, 12 Feb 2023 00:03:29 -0500 Subject: [PATCH 1/2] nip26 initial commit --- nip26/nip26.go | 257 ++++++++++++++++++++++++++++++++++++++++++++ nip26/nip26_test.go | 48 +++++++++ 2 files changed, 305 insertions(+) create mode 100644 nip26/nip26.go create mode 100644 nip26/nip26_test.go diff --git a/nip26/nip26.go b/nip26/nip26.go new file mode 100644 index 0000000..b33d96b --- /dev/null +++ b/nip26/nip26.go @@ -0,0 +1,257 @@ +package nip26 + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "strconv" + "strings" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/nbd-wtf/go-nostr" +) + +type DelegationToken struct { + delegator [32]byte + token [64]byte + kinds []int + since *time.Time + until *time.Time + tag [4]string +} + +// Tag() returns the nostr formatted delegation tag for the DelegationToken d. +func (d *DelegationToken) Tag() nostr.Tag { + return nostr.Tag(d.tag[:]) +} + +// Conditions() is the delegation conditions string as in NIP-26. +func (d *DelegationToken) Conditions() (conditions string) { + for _, k := range d.kinds { + conditions += fmt.Sprintf("kind=%d&", k) + } + if d.since != nil { + conditions += fmt.Sprintf("created_at>%d&", d.since.Unix()) + } + if d.until != nil { + conditions += fmt.Sprintf("created_at<%d&", d.until.Unix()) + } + return strings.TrimSuffix(conditions, "&") +} + +// This error is returned by d.Parse(ev) if the event ev does not have a delegation token. +var NoDelegationTag error = fmt.Errorf("No Delegation Tag") + +// This error is returned by Import(t,delegatee_pk) if the token delegatee does not match delegatee_pk. +var WrongDelegatee error = fmt.Errorf("Wrong Delegatee") + +// CheckDelegation reads the event and reports whether or not it is correctly delegated. +// If there is a delegation tag, the delegation token signature will be checked according to NIP-26. +// If there is no delegation tag, ok will be true and err will be nil. +// For checking many events, it is advisable to use Parse to reduce the number of memory allocations. +func CheckDelegation(ev *nostr.Event) (ok bool, err error) { + d := new(DelegationToken) + if ok, err := d.Parse(ev); ok == true && (err == nil || err == NoDelegationTag) { + return true, nil + } else { + return false, err + } +} + +// Import verifies that t is NIP-26 delegation token for the given delegatee. +// The returned DelegationToken object can be used in DelegatedSign +// If the delegatee implicit in the token does not match delegatee_pk, WrongDelegatee error will be returned. +func Import(t nostr.Tag, delegatee_pk string) (d *DelegationToken, e error) { + d = new(DelegationToken) + if len(t) == 4 && t[0] == "delegation" { + copy(d.tag[:], t) + } else { + return nil, fmt.Errorf("not a delegation tag") + } + if n, e := hex.Decode(d.delegator[:], []byte(d.tag[1])); n != 32 || e != nil { + return nil, fmt.Errorf("invalid delegation tag") + } + if n, e := hex.Decode(d.token[:], []byte(d.tag[3])); n != 64 || e != nil { + return nil, fmt.Errorf("invalid delegation tag") + } + if d.kinds, d.since, d.until, e = parseConditions(d.tag[2]); e != nil { + return nil, fmt.Errorf("invalid conditions string") + } + + // compute the digest + h := sha256.Sum256([]byte(fmt.Sprintf("nostr:delegation:%s:%s", delegatee_pk, d.tag[2]))) + + sig, err := schnorr.ParseSignature(d.token[:]) + if err != nil { + return nil, fmt.Errorf("Error: %s", err.Error()) + } + + pubkey, err := schnorr.ParsePubKey(d.delegator[:]) + if err != nil { + return nil, fmt.Errorf("Error: %s", err.Error()) + } + if !sig.Verify(h[:], pubkey) { + return nil, WrongDelegatee + } + return d, nil +} + +// Parse reads the event ev and stores the delegation token into d. +// The ok value verifies the event is correctly delegated. +// If there is no delegation token, then d will not be changed. In this case the error value will be `NoDelegationTag`, and ok will be set to true. +// Parse does NOT verify the event was correctly signed. Use ev.CheckSignature() for this. +// More efficient memory allocations versus CheckDelegation(ev) if many events need to be checked. +func (d *DelegationToken) Parse(ev *nostr.Event) (ok bool, err error) { + for _, t := range ev.Tags { + if t[0] == "delegation" && len(t) == 4 { + copy(d.tag[:], t) + goto jump1 + } + } + // event has no delegation. set the token to nil and return. + return true, NoDelegationTag + +jump1: + if n, e := hex.Decode(d.delegator[:], []byte(d.tag[1])); n != 32 || e != nil { + return false, fmt.Errorf("invalid delegation tag") + } + if n, e := hex.Decode(d.token[:], []byte(d.tag[3])); n != 64 || e != nil { + return false, fmt.Errorf("invalid delegation tag") + } + + if d.kinds, d.since, d.until, err = parseConditions(d.tag[2]); err != nil { + return false, fmt.Errorf("invalid conditions string") + } + + if len(d.kinds) > 0 { + for _, k := range d.kinds { + if ev.Kind == k { + goto jump2 + } + } + return false, fmt.Errorf("Event kind %d is not allowed in delegation conditions.", ev.Kind) + } + +jump2: + if d.since != nil && ev.CreatedAt.Before(*d.since) { + return false, fmt.Errorf("Event is created before delegation conditions allow.") + } + if d.until != nil && ev.CreatedAt.After(*d.until) { + return false, fmt.Errorf("Event is created after delegation conditions allow.") + } + + // compute the digest + h := sha256.Sum256([]byte(fmt.Sprintf("nostr:delegation:%s:%s", ev.PubKey, d.tag[2]))) + + sig, err := schnorr.ParseSignature(d.token[:]) + if err != nil { + return false, fmt.Errorf("Error: %s", err.Error()) + } + + pubkey, err := schnorr.ParsePubKey(d.delegator[:]) + if err != nil { + return false, fmt.Errorf("Error: %s", err.Error()) + } + + return sig.Verify(h[:], pubkey), nil +} + +// DelegatedSign performs a delegated signature on the event ev. +// The delegation signature is not verified. If desired, the caller can ensure the delegation signature is correct by calling d.Parse(ev) or CheckDelegation(ev) afterwards. +func DelegatedSign(ev *nostr.Event, d *DelegationToken, delegatee_sk string) error { + for _, t := range ev.Tags { + if t[0] == "delegation" { + return fmt.Errorf("event already has delegation token") + } + } + if d.since != nil && ev.CreatedAt.Before(*d.since) || d.until != nil && ev.CreatedAt.After(*d.until) { + return fmt.Errorf("event created_at field is not compatible with delegation token time conditions.") + } + if len(d.kinds) > 0 { + for _, k := range d.kinds { + if ev.Kind == k { + goto jump + } + } + return fmt.Errorf("event kind %d is not compatible with delegation token conditions.", ev.Kind) + } +jump: + if pk, e := nostr.GetPublicKey(delegatee_sk); e != nil { + return fmt.Errorf("invalid delegatee secret key.") + } else { + ev.PubKey = pk + } + ev.Tags = append(ev.Tags, d.Tag()) + return ev.Sign(delegatee_sk) +} + +// CreateToken creates a DelegationToken according to NIP-26. +func CreateToken(delegator_sk string, delegatee_pk string, kinds []int, since *time.Time, until *time.Time) (d *DelegationToken, e error) { + d = new(DelegationToken) + s, e := hex.DecodeString(delegator_sk) + if e != nil { + return nil, fmt.Errorf("invalid delegator secret key") + } + + tee_pk, e := hex.DecodeString(delegatee_pk) + if len(tee_pk) != 32 || e != nil { + return nil, fmt.Errorf("invalid delegatee pubkey") + } + + // set delegator + sk, tor_pk := btcec.PrivKeyFromBytes(s) + copy(d.delegator[:], schnorr.SerializePubKey(tor_pk)) + + d.since = since + d.until = until + + // generate tag + d.tag[0] = "delegation" + d.tag[1] = fmt.Sprintf("%x", d.delegator) + d.tag[2] = d.Conditions() + + h := sha256.Sum256([]byte(fmt.Sprintf("nostr:delegation:%x:%s", tee_pk, d.tag[2]))) + + if sig, err := schnorr.Sign(sk, h[:]); err != nil { + panic(err) + } else { + copy(d.token[:], sig.Serialize()) + } + + d.tag[3] = fmt.Sprintf("%x", d.token) + + return d, nil +} + +func parseConditions(conditions string) (kinds []int, since *time.Time, until *time.Time, err error) { + kinds = make([]int, 0) + for _, v := range strings.Split(conditions, "&") { + switch { + case strings.HasPrefix(v, "kind="): + if i, e := strconv.ParseInt(strings.TrimPrefix(v, "kind="), 10, 64); e == nil { + kinds = append(kinds, int(i)) + } else { + return nil, nil, nil, fmt.Errorf("Invalid: %s!", v) + } + case strings.HasPrefix(v, "created_at>") && since == nil: + if i, e := strconv.ParseInt(strings.TrimPrefix(v, "created_at>"), 10, 64); e == nil { + t := time.Unix(i, 0) + since = &t + } else { + return nil, nil, nil, fmt.Errorf("Invalid: %s", v) + } + case strings.HasPrefix(v, "created_at<") && until == nil: + if i, e := strconv.ParseInt(strings.TrimPrefix(v, "created_at<"), 10, 64); e == nil { + t := time.Unix(i, 0) + until = &t + } else { + return nil, nil, nil, fmt.Errorf("Invalid: %s", v) + } + default: + return nil, nil, nil, fmt.Errorf("Invalid: %s", v) + } + } + return +} diff --git a/nip26/nip26_test.go b/nip26/nip26_test.go new file mode 100644 index 0000000..6ad7354 --- /dev/null +++ b/nip26/nip26_test.go @@ -0,0 +1,48 @@ +package nip26 + +import ( + "github.com/nbd-wtf/go-nostr" + "testing" + "time" +) + +func TestDelegateSign(t *testing.T) { + since := time.Unix(1600000000, 0) + delegator_secret_key, delegatee_secret_key := "3f0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459da", "e9142f724955c5854de36324dab0434f97b15ec6b33464d56ebe491e3f559d1b" + delegatee_pubkey, _ := nostr.GetPublicKey(delegatee_secret_key) + d1, err := CreateToken(delegator_secret_key, delegatee_pubkey, []int{1, 2, 3}, &since, nil) + if err != nil { + t.Error(err) + } + ev := &nostr.Event{} + ev.CreatedAt = time.Now() + ev.Content = "hello world" + ev.Kind = 1 + if err != nil { + t.Error(err) + } + d2, err := Import(d1.Tag(), delegatee_pubkey) + if err != nil { + t.Error(err) + } + + if err = DelegatedSign(ev, d2, delegatee_secret_key); err != nil { + t.Error(err) + } + if ok, err := CheckDelegation(ev); err != nil || ok == false { + t.Error(err) + } + + tag := []string{"delegation", "9ea72be3fcfe38103195a41b67b6f96c14ed92d2091d6d9eb8166a5c27b0c35d", "created_at>1600000000", "c9c71d249455237c0fb620f5d1d271c1b937c1c10ee96ba1932737b0ffc3cfd49ebd918393bf4154ee867f919e56854d7b55adb19357e484d47c1f07ad29e9a9"} + d3, err := Import(tag, delegatee_pubkey) + if err != nil { + t.Error(err) + } + ev.Tags = nil + if err = DelegatedSign(ev, d3, delegatee_secret_key); err != nil { + t.Error(err) + } + if ok, err := d3.Parse(ev); err != nil || ok == false { + t.Error(err) + } +} From 442fe39867dfa867e89c75ad34123726dcb017e4 Mon Sep 17 00:00:00 2001 From: Dylan Cant Date: Sun, 12 Feb 2023 00:39:29 -0500 Subject: [PATCH 2/2] small edits --- nip26/nip26.go | 11 ++++++----- nip26/nip26_test.go | 8 +++++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/nip26/nip26.go b/nip26/nip26.go index b33d96b..f5da8d7 100644 --- a/nip26/nip26.go +++ b/nip26/nip26.go @@ -44,8 +44,8 @@ func (d *DelegationToken) Conditions() (conditions string) { // This error is returned by d.Parse(ev) if the event ev does not have a delegation token. var NoDelegationTag error = fmt.Errorf("No Delegation Tag") -// This error is returned by Import(t,delegatee_pk) if the token delegatee does not match delegatee_pk. -var WrongDelegatee error = fmt.Errorf("Wrong Delegatee") +// This error is returned by Import(t,delegatee_pk) if the token signature verification fails. +var VerificationFailed error = fmt.Errorf("VerificationFailed") // CheckDelegation reads the event and reports whether or not it is correctly delegated. // If there is a delegation tag, the delegation token signature will be checked according to NIP-26. @@ -61,8 +61,8 @@ func CheckDelegation(ev *nostr.Event) (ok bool, err error) { } // Import verifies that t is NIP-26 delegation token for the given delegatee. -// The returned DelegationToken object can be used in DelegatedSign -// If the delegatee implicit in the token does not match delegatee_pk, WrongDelegatee error will be returned. +// The returned DelegationToken object can be used in DelegatedSign. +// If the token signature verification fails, the error VerificationFailed will be returned. func Import(t nostr.Tag, delegatee_pk string) (d *DelegationToken, e error) { d = new(DelegationToken) if len(t) == 4 && t[0] == "delegation" { @@ -93,7 +93,7 @@ func Import(t nostr.Tag, delegatee_pk string) (d *DelegationToken, e error) { return nil, fmt.Errorf("Error: %s", err.Error()) } if !sig.Verify(h[:], pubkey) { - return nil, WrongDelegatee + return nil, VerificationFailed } return d, nil } @@ -204,6 +204,7 @@ func CreateToken(delegator_sk string, delegatee_pk string, kinds []int, since *t sk, tor_pk := btcec.PrivKeyFromBytes(s) copy(d.delegator[:], schnorr.SerializePubKey(tor_pk)) + d.kinds = kinds d.since = since d.until = until diff --git a/nip26/nip26_test.go b/nip26/nip26_test.go index 6ad7354..cb01a32 100644 --- a/nip26/nip26_test.go +++ b/nip26/nip26_test.go @@ -8,14 +8,15 @@ import ( func TestDelegateSign(t *testing.T) { since := time.Unix(1600000000, 0) + until := time.Unix(1600000100, 0) delegator_secret_key, delegatee_secret_key := "3f0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459da", "e9142f724955c5854de36324dab0434f97b15ec6b33464d56ebe491e3f559d1b" delegatee_pubkey, _ := nostr.GetPublicKey(delegatee_secret_key) - d1, err := CreateToken(delegator_secret_key, delegatee_pubkey, []int{1, 2, 3}, &since, nil) + d1, err := CreateToken(delegator_secret_key, delegatee_pubkey, []int{1, 2, 3}, &since, &until) if err != nil { t.Error(err) } ev := &nostr.Event{} - ev.CreatedAt = time.Now() + ev.CreatedAt = time.Unix(1600000050, 0) ev.Content = "hello world" ev.Kind = 1 if err != nil { @@ -33,11 +34,12 @@ func TestDelegateSign(t *testing.T) { t.Error(err) } - tag := []string{"delegation", "9ea72be3fcfe38103195a41b67b6f96c14ed92d2091d6d9eb8166a5c27b0c35d", "created_at>1600000000", "c9c71d249455237c0fb620f5d1d271c1b937c1c10ee96ba1932737b0ffc3cfd49ebd918393bf4154ee867f919e56854d7b55adb19357e484d47c1f07ad29e9a9"} + tag := []string{"delegation", "9ea72be3fcfe38103195a41b67b6f96c14ed92d2091d6d9eb8166a5c27b0c35d", "kind=1&kind=2&kind=3&created_at>1600000000", "8432b8c86f789c2783ef3becb0fabf4def6031c6a615fa7a622f31329d80ed1b2a79ab753c0462f1440503c94e1829158a3a854a1d418ad256ae2cf8aa19fa9a"} d3, err := Import(tag, delegatee_pubkey) if err != nil { t.Error(err) } + ev.Tags = nil if err = DelegatedSign(ev, d3, delegatee_secret_key); err != nil { t.Error(err)