@@ -3995,6 +3995,259 @@ func (r *rpcServer) UpdateSupplyCommit(ctx context.Context,
39953995 return & unirpc.UpdateSupplyCommitResponse {}, nil
39963996}
39973997
3998+ // inclusionProofs fetches the inclusion proofs for the given leaf keys from
3999+ // the provided MSSMT tree. The leaf keys are expected to be 32-byte long.
4000+ func inclusionProofs (ctx context.Context , tree mssmt.Tree ,
4001+ leafKeys [][]byte ) ([][]byte , error ) {
4002+
4003+ proofs := make ([][]byte , 0 , len (leafKeys ))
4004+ for idx := range leafKeys {
4005+ leafKey := leafKeys [idx ]
4006+
4007+ if len (leafKey ) != 32 {
4008+ return nil , fmt .Errorf ("leaf key is not 32 bytes long" )
4009+ }
4010+
4011+ var key [32 ]byte
4012+ copy (key [:], leafKey )
4013+
4014+ inclusionProof , err := tree .MerkleProof (ctx , key )
4015+ if err != nil {
4016+ return nil , fmt .Errorf ("failed to get inclusion proof " +
4017+ "for leaf key %x: %w" , leafKey , err )
4018+ }
4019+
4020+ proofBytes , err := marshalMssmtProof (inclusionProof )
4021+ if err != nil {
4022+ return nil , fmt .Errorf ("failed to marshal inclusion " +
4023+ "proof for leaf key %x: %w" , leafKey , err )
4024+ }
4025+
4026+ proofs = append (proofs , proofBytes )
4027+ }
4028+
4029+ return proofs , nil
4030+ }
4031+
4032+ // supplySubtreeRoot fetches the root of a specific supply subtree and its
4033+ // supply tree inclusion proof.
4034+ func supplySubtreeRoot (ctx context.Context , supplyTree mssmt.Tree ,
4035+ subtrees supplycommit.SupplyTrees ,
4036+ subtreeType supplycommit.SupplySubTree ) (
4037+ * unirpc.SupplyCommitSubtreeRoot , error ) {
4038+
4039+ // Fetch supply subtree root for the given subtree type.
4040+ subtree := subtrees [subtreeType ]
4041+ subtreeRoot , err := subtree .Root (ctx )
4042+ if err != nil {
4043+ return nil , fmt .Errorf ("failed to get supply subtree root: " +
4044+ "%w" , err )
4045+ }
4046+
4047+ // Fetch subtree inclusion proof for the given subtree type. This can
4048+ // be used to verify that the subtree root is indeed part of the
4049+ // supply tree.
4050+ subtreeRootLeafKey := subtreeType .UniverseKey ()
4051+ subtreeInclusionProof , err := supplyTree .MerkleProof (
4052+ ctx , subtreeRootLeafKey ,
4053+ )
4054+ if err != nil {
4055+ return nil , fmt .Errorf ("failed to get supply subtree " +
4056+ "inclusion proof: %w" , err )
4057+ }
4058+
4059+ inclusionProofBytes , err := marshalMssmtProof (subtreeInclusionProof )
4060+ if err != nil {
4061+ return nil , fmt .Errorf ("failed to marshal inclusion " +
4062+ "proof for issuance subtree: %w" , err )
4063+ }
4064+
4065+ return & unirpc.SupplyCommitSubtreeRoot {
4066+ Type : subtreeType .String (),
4067+ RootNode : marshalMssmtNode (subtreeRoot ),
4068+ SupplyTreeLeafKey : subtreeRootLeafKey [:],
4069+ SupplyTreeInclusionProof : inclusionProofBytes ,
4070+ }, nil
4071+ }
4072+
4073+ // FetchSupplyCommit fetches the on-chain supply commitment for a specific
4074+ // asset group.
4075+ func (r * rpcServer ) FetchSupplyCommit (ctx context.Context ,
4076+ req * unirpc.FetchSupplyCommitRequest ) (
4077+ * unirpc.FetchSupplyCommitResponse , error ) {
4078+
4079+ // Parse asset group key from the request.
4080+ var groupPubKey btcec.PublicKey
4081+
4082+ switch {
4083+ case len (req .GetGroupKeyBytes ()) > 0 :
4084+ gk , err := btcec .ParsePubKey (req .GetGroupKeyBytes ())
4085+ if err != nil {
4086+ return nil , fmt .Errorf ("parsing group key: %w" , err )
4087+ }
4088+
4089+ groupPubKey = * gk
4090+
4091+ case len (req .GetGroupKeyStr ()) > 0 :
4092+ groupKeyBytes , err := hex .DecodeString (req .GetGroupKeyStr ())
4093+ if err != nil {
4094+ return nil , fmt .Errorf ("decoding group key: %w" , err )
4095+ }
4096+
4097+ gk , err := btcec .ParsePubKey (groupKeyBytes )
4098+ if err != nil {
4099+ return nil , fmt .Errorf ("parsing group key: %w" , err )
4100+ }
4101+
4102+ groupPubKey = * gk
4103+
4104+ default :
4105+ return nil , fmt .Errorf ("group key unspecified" )
4106+ }
4107+
4108+ // Formulate an asset specifier from the asset group key.
4109+ assetSpec := asset .NewSpecifierFromGroupKey (groupPubKey )
4110+
4111+ // Fetch the supply commitment for the asset specifier.
4112+ respOpt , err := r .cfg .SupplyCommitManager .FetchCommitment (
4113+ ctx , assetSpec ,
4114+ )
4115+ if err != nil {
4116+ return nil , fmt .Errorf ("failed to fetch supply commit: %w" ,
4117+ err )
4118+ }
4119+ if respOpt .IsNone () {
4120+ return nil , fmt .Errorf ("supply commitment not found for " +
4121+ "asset group with key %x" ,
4122+ groupPubKey .SerializeCompressed ())
4123+ }
4124+ resp , err := respOpt .UnwrapOrErr (fmt .Errorf ("unexpected None value " +
4125+ "for supply commitment response" ))
4126+ if err != nil {
4127+ return nil , err
4128+ }
4129+
4130+ supplyTreeRoot , err := resp .SupplyTree .Root (ctx )
4131+ if err != nil {
4132+ return nil , fmt .Errorf ("failed to get supply tree root: %w" ,
4133+ err )
4134+ }
4135+
4136+ // Fetch subtree commitment root and inclusion proofs for the issuance
4137+ // subtree.
4138+ rpcIssuanceSubtreeRoot , err := supplySubtreeRoot (
4139+ ctx , resp .SupplyTree , resp .Subtrees , supplycommit .MintTreeType ,
4140+ )
4141+ if err != nil {
4142+ return nil , fmt .Errorf ("failed to fetch supply issuance " +
4143+ "subtree root: %w" , err )
4144+ }
4145+
4146+ // Fetch subtree commitment root and inclusion proofs for the burn
4147+ // subtree.
4148+ rpcBurnSubtreeRoot , err := supplySubtreeRoot (
4149+ ctx , resp .SupplyTree , resp .Subtrees , supplycommit .BurnTreeType ,
4150+ )
4151+ if err != nil {
4152+ return nil , fmt .Errorf ("failed to fetch supply burn subtree " +
4153+ "root: %w" , err )
4154+ }
4155+
4156+ // Fetch subtree commitment root and inclusion proofs for the ignore
4157+ // subtree.
4158+ rpcIgnoreSubtreeRoot , err := supplySubtreeRoot (
4159+ ctx , resp .SupplyTree , resp .Subtrees ,
4160+ supplycommit .IgnoreTreeType ,
4161+ )
4162+ if err != nil {
4163+ return nil , fmt .Errorf ("failed to fetch supply ignore subtree " +
4164+ "root: %w" , err )
4165+ }
4166+
4167+ // Get inclusion proofs for any issuance leaf key specified in the
4168+ // request.
4169+ issuanceTree := resp .Subtrees [supplycommit .MintTreeType ]
4170+ issuanceInclusionProofs , err := inclusionProofs (
4171+ ctx , issuanceTree , req .IssuanceLeafKeys ,
4172+ )
4173+ if err != nil {
4174+ return nil , fmt .Errorf ("failed to fetch issuance tree " +
4175+ "inclusion proofs: %w" , err )
4176+ }
4177+
4178+ // Get inclusion proofs for any burn leaf key specified in the request.
4179+ burnTree := resp .Subtrees [supplycommit .BurnTreeType ]
4180+ burnInclusionProofs , err := inclusionProofs (
4181+ ctx , burnTree , req .BurnLeafKeys ,
4182+ )
4183+ if err != nil {
4184+ return nil , fmt .Errorf ("failed to fetch burn tree " +
4185+ "inclusion proofs: %w" , err )
4186+ }
4187+
4188+ // Get inclusion proofs for any ignore leaf key specified in the
4189+ // request.
4190+ ignoreTree := resp .Subtrees [supplycommit .IgnoreTreeType ]
4191+ ignoreInclusionProofs , err := inclusionProofs (
4192+ ctx , ignoreTree , req .IgnoreLeafKeys ,
4193+ )
4194+ if err != nil {
4195+ return nil , fmt .Errorf ("failed to fetch ignore tree " +
4196+ "inclusion proofs: %w" , err )
4197+ }
4198+
4199+ // Sanity check: ensure the supply root derived from the supply tree
4200+ // matches the root provided in the chain commitment.
4201+ if resp .ChainCommitment .SupplyRoot .NodeHash () !=
4202+ supplyTreeRoot .NodeHash () {
4203+
4204+ return nil , fmt .Errorf ("mismatched supply commitment root: " +
4205+ "expected %x, got %x" ,
4206+ resp .ChainCommitment .SupplyRoot .NodeHash (),
4207+ supplyTreeRoot .NodeHash ())
4208+ }
4209+
4210+ txOutInternalKey := resp .ChainCommitment .InternalKey .PubKey
4211+
4212+ // Extract the block height and hash from the chain commitment if
4213+ // present.
4214+ var (
4215+ blockHeight uint32
4216+ blockHash []byte
4217+ txIndex uint32
4218+ chainFees int64
4219+ )
4220+ resp .ChainCommitment .CommitmentBlock .WhenSome (
4221+ func (b supplycommit.CommitmentBlock ) {
4222+ blockHeight = b .Height
4223+ blockHash = b .Hash [:]
4224+ txIndex = b .TxIndex
4225+ chainFees = b .ChainFees
4226+ },
4227+ )
4228+
4229+ return & unirpc.FetchSupplyCommitResponse {
4230+ SupplyCommitmentRoot : marshalMssmtNode (supplyTreeRoot ),
4231+
4232+ AnchorTxid : resp .ChainCommitment .Txn .TxID (),
4233+ AnchorTxOutIdx : resp .ChainCommitment .TxOutIdx ,
4234+ AnchorTxOutInternalKey : txOutInternalKey .SerializeCompressed (),
4235+
4236+ BlockHeight : blockHeight ,
4237+ BlockHash : blockHash ,
4238+ BlockTxIndex : txIndex ,
4239+ TxChainFeesSats : chainFees ,
4240+
4241+ IssuanceSubtreeRoot : rpcIssuanceSubtreeRoot ,
4242+ BurnSubtreeRoot : rpcBurnSubtreeRoot ,
4243+ IgnoreSubtreeRoot : rpcIgnoreSubtreeRoot ,
4244+
4245+ IssuanceLeafInclusionProofs : issuanceInclusionProofs ,
4246+ BurnLeafInclusionProofs : burnInclusionProofs ,
4247+ IgnoreLeafInclusionProofs : ignoreInclusionProofs ,
4248+ }, nil
4249+ }
4250+
39984251// SubscribeSendAssetEventNtfns registers a subscription to the event
39994252// notification stream which relates to the asset sending process.
40004253func (r * rpcServer ) SubscribeSendAssetEventNtfns (
@@ -4017,7 +4270,9 @@ func (r *rpcServer) SubscribeReceiveAssetEventNtfns(
40174270 _ * tapdevrpc.SubscribeReceiveAssetEventNtfnsRequest ,
40184271 ntfnStream devReceiveEventStream ) error {
40194272
4020- marshaler := func (event fn.Event ) (* tapdevrpc.ReceiveAssetEvent , error ) {
4273+ marshaler := func (event fn.Event ) (* tapdevrpc.ReceiveAssetEvent ,
4274+ error ) {
4275+
40214276 return marshallReceiveAssetEvent (
40224277 event , r .cfg .TapAddrBook ,
40234278 )
0 commit comments